diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..cd398190e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,64 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto +*.md text + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +*.cs text diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..06cb15ebc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: openperpetuum +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..8da04976c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,59 @@ +name: OpenPerpetuum Server Compile + +on: + push: + branches: [ "Development" ] + pull_request: + branches: [ "Development" ] + workflow_dispatch: + inputs: + debug_enabled: + type: boolean + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false + +jobs: + + build: + + strategy: + matrix: + configuration: [Debug, Release] + + runs-on: windows-2019 # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + env: + Solution_Name: Perpetuum.sln # Replace with your solution name, i.e. MyWpfApp.sln. + Configuration: ${{ matrix.configuration }} + Workspace: ${{ github.workspace }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v2 + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore&Build the application for (${{ matrix.configuration }}) + run: | + nuget restore + msbuild ${{ env.Solution_Name }} -p:Configuration=${{ env.Configuration }} + + # Enable tmate debugging of manually-triggered workflows if the input option was provided + # this allows us to debug the file paths + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && always()}} + + - name: Upload a Build Artifact for (${{ matrix.configuration }}) + uses: actions/upload-artifact@v4 + with: + name: Perpetuum-Server-${{ github.sha }}.${{ matrix.configuration }} + path: ${{ env.Workspace }}/bin/x64/${{ env.Configuration }}/Perpetuum.Server + if: ${{ github.event_name == 'push'}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d58682c18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,262 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# 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 +# TODO: 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 +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable 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 + +# 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 +node_modules/ +orleans.codegen.cs + +# 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 + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# 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/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +/PerpetuumTests +.nuget/nuget.exe diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe deleted file mode 100644 index 8d13fd88c..000000000 Binary files a/.nuget/NuGet.exe and /dev/null differ diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets index d3befdac2..45f68dfc1 100644 --- a/.nuget/NuGet.targets +++ b/.nuget/NuGet.targets @@ -12,8 +12,8 @@ true - - false + + true @@ -117,9 +117,9 @@ try { OutputFilename = Path.GetFullPath(OutputFilename); - Log.LogMessage("Downloading latest version of NuGet.exe..."); + Log.LogMessage("Downloading latest version of nuget.exe..."); WebClient webClient = new WebClient(); - webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFilename); + webClient.DownloadFile("https://dist.nuget.org/win-x86-commandline/latest/nuget.exe", OutputFilename); return true; } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..55c6c4b50 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Contributing to OpenPerpetuumServer + +We generally follow a simplified gitflow model, with some coupling to other repository states, like the OPDB. + +To contribute: + - Branch from the most recent Development + - Commit your code, push and submit a Pull Request to Development + +Your code will be reviewed and merged if it does the following. +Either: + - Addresses or implements an OP-Project identified bug, feature, or change. + - Enhances, optimizes, or extends existing functionality. + - Refactors that improves maintainability of the codebase. + +Your code must: + - Not break style conventions within the codebase. + - Be neither ungrokable, insufficiently documented (self-/comments) nor otherwise unmaintainable by other experienced developers. + - Not introduce, change, or manipulate game mechanics outside the scope of OP-Project issue cards. + - Not introduce, exploit, or otherwise attempt to introduce vulnerabilities to compromise game integrity or user data. + +Reviewers will point out when these tenets are not followed, but it will save everyone time if you adhere to these rules the first time. + +Special Notes: +The PerpetuumServer is highly coupled in some instances to DB state. The OPDB has its own patching source control model which you should learn and follow. If your pull request also requires database changes: + - Submit your PR to the server, and another PR of your SQL patch to the OPDB development branch + - **Include a comment in the PR note that the server and DB patch are interdependent** + +## Org-Project: cross repo issue tracking +As mentioned above, all action items are collected in one kanban board for Server related development. These issues should be pointed to with all PR's made. A PR should address at most one issue. When a server change requires a DB change, use the issue too as a mechanism to indicate the coupling. +[See issues here](https://github.com/orgs/OpenPerpetuum/projects/2) + +## Code of Conduct + +Your code, comments, commit messages, and everything you do on the internet will reflect on you and how others perceive you. Act accordingly. +Trolling, harassment or abusive behaviour of any kind will not be tolerated. +This is an open source, volunteer project. Respect people's time and privacy. + +## The Team + +We have a github group (fancy!) which is a subset of our full project team and includes all of our active contributing development team members and some design members. +To join, hop on the discord, or find our volunteer survey, and speak with a member of the team. + +Warning: we do have standards. You will have to actually have to do something to get on the team. +Reading this is the first step, let us know you managed to get this far. \ No newline at end of file diff --git a/LICENSE b/LICENSE index f3cfd2a7c..547922963 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,7 @@ +---------------------------------------------------------------------------------- +Perpetuum - LICENSE +---------------------------------------------------------------------------------- + MIT License Copyright (c) 2018 Perpetuum @@ -19,3 +23,30 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------------------------------------------------- +OpenPerpetuum - LICENSE +---------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2018-2022 OpenPerpetuum + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. All copies or substantial portions +of the Software will include attributions to the Open Perpetuum Project. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Perpetuum.sln b/Perpetuum.sln index a4f3593a4..bab491d9c 100644 --- a/Perpetuum.sln +++ b/Perpetuum.sln @@ -26,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perpetuum.AdminTool", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perpetuum.Bootstrapper", "src\Perpetuum.Bootstrapper\Perpetuum.Bootstrapper.csproj", "{E113962C-AD5D-4F17-8D08-656221CBD2D4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perpetuum.ServerService", "src\Perpetuum.ServerService\Perpetuum.ServerService.csproj", "{9C77CE95-825E-4BB1-9129-66835BDB8C96}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -108,6 +110,18 @@ Global {E113962C-AD5D-4F17-8D08-656221CBD2D4}.Release|x64.Build.0 = Release|Any CPU {E113962C-AD5D-4F17-8D08-656221CBD2D4}.Release|x86.ActiveCfg = Release|Any CPU {E113962C-AD5D-4F17-8D08-656221CBD2D4}.Release|x86.Build.0 = Release|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Debug|x64.ActiveCfg = Debug|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Debug|x64.Build.0 = Debug|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Debug|x86.ActiveCfg = Debug|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Debug|x86.Build.0 = Debug|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Release|Any CPU.Build.0 = Release|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Release|x64.ActiveCfg = Release|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Release|x64.Build.0 = Release|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Release|x86.ActiveCfg = Release|Any CPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 7ae3e95a7..727a73102 100644 Binary files a/README.md and b/README.md differ diff --git a/opp-server.jpeg b/opp-server.jpeg new file mode 100644 index 000000000..531d18376 Binary files /dev/null and b/opp-server.jpeg differ diff --git a/src/Perpetuum.AdminTool/LocalServerRunner.cs b/src/Perpetuum.AdminTool/LocalServerRunner.cs index 602101bdf..18161f893 100644 --- a/src/Perpetuum.AdminTool/LocalServerRunner.cs +++ b/src/Perpetuum.AdminTool/LocalServerRunner.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; @@ -17,6 +18,8 @@ public class LocalServerRunner private bool _stopProbing; private LogHandler _log; private LocalServerState _localServerState; + + private Dictionary _loadedConfig; public LocalServerState State { get => _localServerState; @@ -93,13 +96,25 @@ public void Run(string gameRoot, string pathToExecutable) // save localserverinfo file SaveLocalServerInfo(gameRoot, pathToExecutable); - var listeningPort = GetPortFromIni(gameRoot); + _loadedConfig = ReadConfigFromDisk(gameRoot); + + var listeningPort = _loadedConfig["listenerPort"]; if (listeningPort <= 0) { _log.StatusError($"Wasn't able to get listening port from {PERPETUUMINIFILE}. No port probe is started."); return; } + var adminOnlyMode = _loadedConfig["adminOnlyMode"]; + if (!adminOnlyMode) + { + _log.Log("AdminOnlyMode: Disabled (Default)"); + } + else + { + _log.Log("AdminOnlyMode: Enabled"); + } + //RunNetStatProbe(listeningPort); RunSocketProbe(listeningPort); } @@ -335,26 +350,29 @@ private string NextIndicatorText() return _indicatorPhases[_indicatorTextIndex]; } - private int GetPortFromIni(string gameRoot) + private Dictionary ReadConfigFromDisk(string gameRoot) { var perpetuumIni = Path.Combine(gameRoot, PERPETUUMINIFILE); if (!File.Exists(perpetuumIni)) { - _log.StatusError($"{PERPETUUMINIFILE} was not found in game root {gameRoot} folder. Check your settings!" ); - return -1; + _log.StatusError($"{PERPETUUMINIFILE} was not found in game root {gameRoot} folder. Check your settings!"); } var json = File.ReadAllText(perpetuumIni); - var portData = JsonConvert.DeserializeAnonymousType(json,new + var listenerPort = JsonConvert.DeserializeAnonymousType(json, new { ListenerPort = -1, }); + var adminOnlyMode = JsonConvert.DeserializeAnonymousType(json, new + { + StartServerInAdminOnlyMode = false, + }); - _log.Log($"Relay port loaded from {PERPETUUMINIFILE}: {portData.ListenerPort}"); + Dictionary result = new Dictionary(); + result.Add("listenerPort", listenerPort.ListenerPort); + result.Add("adminOnlyMode", adminOnlyMode.StartServerInAdminOnlyMode); - return portData.ListenerPort; + return result; } - - } } diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index e93e9d1a7..c71b95cdf 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1,18 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Numerics; -using System.Reflection; -using System.Runtime; -using System.Runtime.Caching; -using System.Text; -using System.Threading; -using System.Transactions; -using Autofac; +using Autofac; using Autofac.Builder; using Autofac.Core; using Newtonsoft.Json; @@ -39,6 +25,7 @@ using Perpetuum.IO; using Perpetuum.Items; using Perpetuum.Items.Ammos; +using Perpetuum.Items.Helpers; using Perpetuum.Items.Templates; using Perpetuum.Log; using Perpetuum.Log.Formatters; @@ -76,7 +63,14 @@ using Perpetuum.RequestHandlers.Zone.PBS; using Perpetuum.RequestHandlers.Zone.StatsMapDrawing; using Perpetuum.Robots; +using Perpetuum.Services; using Perpetuum.Services.Channels; +using Perpetuum.Services.Channels.ChatCommands; +using Perpetuum.Services.Daytime; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; +using Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers; using Perpetuum.Services.ExtensionService; using Perpetuum.Services.HighScores; using Perpetuum.Services.Insurance; @@ -97,15 +91,19 @@ using Perpetuum.Services.ProductionEngine.Facilities; using Perpetuum.Services.ProductionEngine.ResearchKits; using Perpetuum.Services.Relay; +using Perpetuum.Services.Relics; using Perpetuum.Services.RiftSystem; +using Perpetuum.Services.RiftSystem.StrongholdRifts; using Perpetuum.Services.Sessions; using Perpetuum.Services.Social; using Perpetuum.Services.Sparks; using Perpetuum.Services.Sparks.Teleports; using Perpetuum.Services.Standing; using Perpetuum.Services.Steam; +using Perpetuum.Services.Strongholds; using Perpetuum.Services.TechTree; using Perpetuum.Services.Trading; +using Perpetuum.Services.Weather; using Perpetuum.Threading.Process; using Perpetuum.Units; using Perpetuum.Units.DockingBases; @@ -116,14 +114,21 @@ using Perpetuum.Zones.CombatLogs; using Perpetuum.Zones.Decors; using Perpetuum.Zones.Effects; +using Perpetuum.Zones.Effects.ZoneEffects; using Perpetuum.Zones.Eggs; using Perpetuum.Zones.Environments; using Perpetuum.Zones.Gates; using Perpetuum.Zones.Intrusion; +using Perpetuum.Zones.LandMines; using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.NpcSystem.Flocks; using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; +using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; +using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; +using Perpetuum.Zones.NpcSystem.Reinforcements; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; using Perpetuum.Zones.PBS; using Perpetuum.Zones.PBS.ArmorRepairers; @@ -139,6 +144,7 @@ using Perpetuum.Zones.PlantTools; using Perpetuum.Zones.ProximityProbes; using Perpetuum.Zones.PunchBags; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Scanning.Ammos; using Perpetuum.Zones.Scanning.Modules; using Perpetuum.Zones.Scanning.Results; @@ -155,6 +161,19 @@ using Perpetuum.Zones.Training; using Perpetuum.Zones.Training.Reward; using Perpetuum.Zones.ZoneEntityRepositories; +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Numerics; +using System.Reflection; +using System.Runtime; +using System.Runtime.Caching; +using System.Text; +using System.Threading; using ChangeAmmo = Perpetuum.RequestHandlers.ChangeAmmo; using CorporationDocumentConfig = Perpetuum.RequestHandlers.Corporations.CorporationDocumentConfig; using EquipAmmo = Perpetuum.RequestHandlers.EquipAmmo; @@ -170,23 +189,22 @@ using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; - namespace Perpetuum.Bootstrapper { - class EntityAggregateServices : IEntityServices + internal class EntityAggregateServices : IEntityServices { public IEntityFactory Factory { get; set; } public IEntityDefaultReader Defaults { get; set; } public IEntityRepository Repository { get; set; } } - class RobotTemplateServicesImpl : IRobotTemplateServices + internal class RobotTemplateServicesImpl : IRobotTemplateServices { public IRobotTemplateReader Reader { get; set; } public IRobotTemplateRelations Relations { get; set; } } - class TeleportStrategyFactoriesImpl : ITeleportStrategyFactories + internal class TeleportStrategyFactoriesImpl : ITeleportStrategyFactories { public TeleportWithinZone.Factory TeleportWithinZoneFactory { get; set; } public TeleportToAnotherZone.Factory TeleportToAnotherZoneFactory { get; set; } @@ -202,51 +220,58 @@ public class PerpetuumBootstrapper public void Start() { - var s = _container.Resolve(); + IHostStateService s = _container.Resolve(); s.State = HostState.Starting; } public void Stop() { - var s = _container.Resolve(); + IHostStateService s = _container.Resolve(); s.State = HostState.Stopping; } public void Stop(TimeSpan delay) { - var m = _container.Resolve(); + HostShutDownManager m = _container.Resolve(); m.Shutdown(delay); } + public IContainer GetContainer() + { + return _container; + } + public void WaitForStop() { - var are = new AutoResetEvent(false); + AutoResetEvent are = new AutoResetEvent(false); - var s = _container.Resolve(); + IHostStateService s = _container.Resolve(); s.StateChanged += (sender, state) => { if (state == HostState.Off) - are.Set(); + { + _ = are.Set(); + } }; - are.WaitOne(); + _ = are.WaitOne(); } public void WriteCommandsToFile(string path) { - var sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - foreach (var command in GetCommands().OrderBy(c => c.Text)) + foreach (Command command in GetCommands().OrderBy(c => c.Text)) { - sb.AppendLine($"{command.Text},{command.AccessLevel}"); + _ = sb.AppendLine($"{command.Text},{command.AccessLevel}"); } - - File.WriteAllText(path,sb.ToString()); + + File.WriteAllText(path, sb.ToString()); } public IEnumerable GetCommands() { - return typeof(Commands).GetFields(BindingFlags.Static | BindingFlags.Public).Select(info => (Command)info.GetValue(null)); + return typeof(Commands).GetFields(BindingFlags.Static | BindingFlags.Public).Select(info => (Command)info.GetValue(null)); } public void Init(string gameRoot) @@ -256,7 +281,7 @@ public void Init(string gameRoot) _container = _builder.Build(); Logger.Current = _container.Resolve>(); - var config = _container.Resolve(); + GlobalConfiguration config = _container.Resolve(); _container.Resolve().State = HostState.Init; @@ -265,10 +290,9 @@ public void Init(string gameRoot) Logger.Info($"GC Latency mode: {GCSettings.LatencyMode}"); Logger.Info($"Vector is hardware accelerated: {Vector.IsHardwareAccelerated}"); - Db.TransactionScopeFactory = _container.Resolve>(); Db.DbQueryFactory = _container.Resolve>(); - using (var connection = _container.Resolve()()) { Logger.Info($"Database: {connection.Database}"); } + using (System.Data.IDbConnection connection = _container.Resolve()()) { Logger.Info($"Database: {connection.Database}"); } InitGame(_container); @@ -303,7 +327,6 @@ public void Init(string gameRoot) MissionTarget.RobotTemplateRelations = _container.Resolve(); MissionTarget.MissionTargetInProgressFactory = _container.Resolve(); - MissionTargetRewardCalculator.Init(_container.Resolve()); MissionTargetSuccessInfoGenerator.Init(_container.Resolve()); MissionBonus.Init(_container.Resolve()); @@ -332,38 +355,46 @@ public void Init(string gameRoot) switch (state) { case HostState.Stopping: - { - _container.Resolve().Stop(); - NatDiscoverer.ReleaseAll(); - sender.State = HostState.Off; - break; - } + { + _container.Resolve().Stop(); + NatDiscoverer.ReleaseAll(); + sender.State = HostState.Off; + break; + } case HostState.Starting: - { - _container.Resolve().Start(); - sender.State = HostState.Online; - break; - } + { + _container.Resolve().Start(); + sender.State = HostState.Online; + break; + } } }; DefaultCorporationDataCache.LoadAll(); _container.Resolve().CacheMissionData(); + // initialize our markets. + // this is dependent on all zones being loaded. + _container.Resolve().Init(); + _container.Resolve().Init(); + + } public bool TryInitUpnp(out bool success) { success = false; - var config = _container.Resolve(); + GlobalConfiguration config = _container.Resolve(); if (!config.EnableUpnp) + { return false; + } try { - var discoverer = new NatDiscoverer(); + NatDiscoverer discoverer = new NatDiscoverer(); NatDiscoverer.ReleaseAll(); - var natDevice = discoverer.DiscoverDeviceAsync().Result; + NatDevice natDevice = discoverer.DiscoverDeviceAsync().Result; if (natDevice == null) { Logger.Error("[UPNP] NAT device not found!"); @@ -372,7 +403,7 @@ public bool TryInitUpnp(out bool success) void Map(int port) { - var task = natDevice.CreatePortMapAsync(new Mapping(Protocol.Tcp,port,port)).ContinueWith(t => + System.Threading.Tasks.Task task = natDevice.CreatePortMapAsync(new Mapping(Protocol.Tcp, port, port)).ContinueWith(t => { Logger.Info($"[UPNP] Port mapped: {port}"); }); @@ -381,7 +412,7 @@ void Map(int port) Map(config.ListenerPort); - foreach (var zone in _container.Resolve().Zones) + foreach (IZone zone in _container.Resolve().Zones) { Map(zone.Configuration.ListenerPort); } @@ -402,12 +433,12 @@ void Map(int port) private static void InitGame(IComponentContext container) { //the current host has to clean up things in the onlinehost table, and other runtime tables - Db.Query().CommandText("initServer").ExecuteNonQuery(); + _ = Db.Query().CommandText("initServer").ExecuteNonQuery(); - var globalConfiguration = container.Resolve(); + GlobalConfiguration globalConfiguration = container.Resolve(); if (!string.IsNullOrEmpty(globalConfiguration.PersonalConfig)) { - Db.Query().CommandText(globalConfiguration.PersonalConfig).ExecuteNonQuery(); + _ = Db.Query().CommandText(globalConfiguration.PersonalConfig).ExecuteNonQuery(); Logger.Info("Personal sp executed:" + globalConfiguration.PersonalConfig); } @@ -424,31 +455,30 @@ private IRegistrationBuilder(TimeSpan.FromSeconds(7)); - RegisterAutoActivate(TimeSpan.FromMinutes(5)); - //RegisterAutoActivate(TimeSpan.FromHours(10)); - RegisterAutoActivate(TimeSpan.FromHours(1)); -// RegisterAutoActivate(TimeSpan.FromSeconds(10)); - RegisterAutoActivate(TimeSpan.FromMinutes(5)); - RegisterAutoActivate(TimeSpan.FromMinutes(10)); - RegisterAutoActivate(TimeSpan.FromMinutes(10)); + _ = RegisterAutoActivate(TimeSpan.FromSeconds(7)); + _ = RegisterAutoActivate(TimeSpan.FromMinutes(5)); + _ = RegisterAutoActivate(TimeSpan.FromHours(1)); + _ = RegisterAutoActivate(TimeSpan.FromMinutes(5)); + _ = RegisterAutoActivate(TimeSpan.FromMinutes(10)); + _ = RegisterAutoActivate(TimeSpan.FromMinutes(10)); + _ = RegisterAutoActivate(TimeSpan.FromHours(7)); } private void RegisterCommands() { - foreach (var command in GetCommands()) + foreach (Command command in GetCommands()) { - _builder.RegisterInstance(command).As().Keyed(command.Text.ToUpper()); + _ = _builder.RegisterInstance(command).As().Keyed(command.Text.ToUpper()); } - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); - return (commandText => + IComponentContext ctx = x.Resolve(); + return commandText => { commandText = commandText.ToUpper(); return ctx.IsRegisteredWithKey(commandText) ? ctx.ResolveKeyed(commandText) : null; - }); + }; }); } @@ -466,263 +496,268 @@ private void InitContainer(string gameRoot) RegisterChannelTypes(); RegisterMtProducts(); RegisterRifts(); + RegisterRelics(); RegisterEffects(); RegisterIntrusions(); RegisterZones(); RegisterPBS(); - _builder.Register>(x => + _ = _builder.Register>(x => { return name => new MemoryCache(name); }); - _builder.RegisterType().AsSelf().As(); - _builder.Register(c => + _ = _builder.RegisterType().AsSelf().As(); + _ = _builder.Register(c => { - var cache = new MemoryCache("CharacterProfiles"); + MemoryCache cache = new MemoryCache("CharacterProfiles"); return new CachedReadOnlyRepository(cache, c.Resolve()); - }).AsSelf().As>().SingleInstance(); + }).AsSelf().As>().SingleInstance(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().OnActivated(e => { e.Instance.Init(); }).As().SingleInstance(); - _builder.RegisterType().As().AutoActivate().OnActivated(e => + _ = _builder.RegisterType().As().AutoActivate().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromHours(1))); }).SingleInstance(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As().SingleInstance().AutoActivate(); - _builder.RegisterType().AsSelf().As(); + _ = _builder.RegisterType().AsSelf().As(); - _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType().As().SingleInstance(); InitRelayManager(); - _builder.Register(c => new FileSystem(gameRoot)).As(); - _builder.Register(c => + _ = _builder.Register(c => new FileSystem(gameRoot)).As(); + _ = _builder.Register(c => { - var fileManager = c.Resolve(); - var settingsFile = fileManager.ReadAllText("perpetuum.ini"); - var configuration = JsonConvert.DeserializeObject(settingsFile); + IFileSystem fileManager = c.Resolve(); + string settingsFile = fileManager.ReadAllText("perpetuum.ini"); + GlobalConfiguration configuration = JsonConvert.DeserializeObject(settingsFile); configuration.GameRoot = gameRoot; return configuration; }).SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType().SingleInstance(); + + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As().SingleInstance(); - _builder.Register(c => + _ = _builder.Register(c => { - var config = c.Resolve(); + GlobalConfiguration config = c.Resolve(); return config.Corporation; }).As(); - _builder.RegisterType().As().SingleInstance(); - _builder.Register(c => new ProcessManager(TimeSpan.FromMilliseconds(50))).As().SingleInstance(); + _ = _builder.RegisterType().As().SingleInstance(); + _ = _builder.Register(c => new ProcessManager(TimeSpan.FromMilliseconds(50))).As().SingleInstance(); - _builder.Register(x => + _ = _builder.Register(x => { - var connectionString = x.Resolve().ConnectionString; - return (() => new SqlConnection(connectionString)); + string connectionString = x.Resolve().ConnectionString; + return () => new SqlConnection(connectionString); }); - _builder.RegisterType(); - - _builder.Register(x => - { - return new TransactionScope(TransactionScopeOption.Required, new TransactionOptions - { - IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted - }); - }); + _ = _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance().OnActivated(e => e.Instance.Init()); - _builder.RegisterType().SingleInstance(); - _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => e.Instance.Init()); + _ = _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType(); // this doesn't appear to be something that should be a singleton. - _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().AsSelf().As(); - _builder.RegisterType(); - _builder.Register(x => + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().AsSelf().As(); + _ = _builder.RegisterType(); + _ = _builder.Register(x => { - var ctx = x.Resolve(); - return ((character, type) => + IComponentContext ctx = x.Resolve(); + return (character, type) => { return ctx.Resolve(new TypedParameter(typeof(Character), character), new TypedParameter(typeof(TransactionType), type)); - }); + }; }); - _builder.RegisterType().AsSelf(); - _builder.Register(x => x.Resolve(TypedParameter.From(0))).Named("nullcharacter").SingleInstance(); + _ = _builder.RegisterType().AsSelf(); + _ = _builder.Register(x => x.Resolve(TypedParameter.From(0))).Named("nullcharacter").SingleInstance(); - _builder.Register(x => + _ = _builder.Register(x => { - var ctx = x.Resolve(); - return (id => + IComponentContext ctx = x.Resolve(); + return id => { - if (id == 0) - return ctx.ResolveNamed("nullcharacter"); - - return ctx.Resolve(TypedParameter.From(id)); - }); + return id == 0 ? ctx.ResolveNamed("nullcharacter") : ctx.Resolve(TypedParameter.From(id)); + }; }); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); - _builder.Register(x => + _ = _builder.Register(x => { - var cfg = x.Resolve(); + GlobalConfiguration cfg = x.Resolve(); return new SteamManager(cfg.SteamAppID, cfg.SteamKey); }).As(); } private void RegisterChannelTypes() { - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance(); - - RegisterRequestHandler(Commands.ChannelCreate); - RegisterRequestHandler(Commands.ChannelList); - RegisterRequestHandler(Commands.ChannelMyList); - RegisterRequestHandler(Commands.ChannelJoin); - RegisterRequestHandler(Commands.ChannelLeave); - RegisterRequestHandler(Commands.ChannelKick); - RegisterRequestHandler(Commands.ChannelTalk); - RegisterRequestHandler(Commands.ChannelSetMemberRole); - RegisterRequestHandler(Commands.ChannelSetPassword); - RegisterRequestHandler(Commands.ChannelSetTopic); - RegisterRequestHandler(Commands.ChannelBan); - RegisterRequestHandler(Commands.ChannelRemoveBan); - RegisterRequestHandler(Commands.ChannelGetBannedMembers); - RegisterRequestHandler(Commands.ChannelGlobalMute); - RegisterRequestHandler(Commands.ChannelGetMutedCharacters); - RegisterRequestHandler(Commands.ChannelCreateForTerminals); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As().SingleInstance(); + + _ = RegisterRequestHandler(Commands.ChannelCreate); + _ = RegisterRequestHandler(Commands.ChannelList); + _ = RegisterRequestHandler(Commands.ChannelListAll); + _ = RegisterRequestHandler(Commands.ChannelMyList); + _ = RegisterRequestHandler(Commands.ChannelJoin); + _ = RegisterRequestHandler(Commands.ChannelLeave); + _ = RegisterRequestHandler(Commands.ChannelKick); + _ = RegisterRequestHandler(Commands.ChannelTalk); + _ = RegisterRequestHandler(Commands.ChannelSetMemberRole); + _ = RegisterRequestHandler(Commands.ChannelSetPassword); + _ = RegisterRequestHandler(Commands.ChannelSetTopic); + _ = RegisterRequestHandler(Commands.ChannelBan); + _ = RegisterRequestHandler(Commands.ChannelRemoveBan); + _ = RegisterRequestHandler(Commands.ChannelGetBannedMembers); + _ = RegisterRequestHandler(Commands.ChannelGlobalMute); + _ = RegisterRequestHandler(Commands.ChannelGetMutedCharacters); + _ = RegisterRequestHandler(Commands.ChannelCreateForTerminals); } private void RegisterEffects() { - _builder.RegisterType(); - - _builder.RegisterType().Keyed(EffectType.effect_invulnerable); - _builder.RegisterType().Keyed(EffectType.effect_eccm); - _builder.RegisterType().Keyed(EffectType.effect_stealth); - - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_core_recharge_time); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_critical_hit_chance); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_locking_time); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_signature_radius); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_fast_extraction); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_core_usage_gathering); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_siege); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_speed); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_repaired_amount); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_locking_range); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_ewar_optimal); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_armor_max); - _builder.RegisterType().Keyed(EffectType.effect_aura_gang_shield_absorbtion_ratio); + _ = _builder.RegisterType(); + + _ = _builder.RegisterType().As(); + + _ = _builder.Register>(x => + { + IComponentContext ctx = x.Resolve(); + return zone => new ZoneEffectHandler(zone); + }); + + _ = _builder.RegisterType().Keyed(EffectType.effect_invulnerable); + _ = _builder.RegisterType().Keyed(EffectType.effect_eccm); + _ = _builder.RegisterType().Keyed(EffectType.effect_stealth); + + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_core_recharge_time); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_critical_hit_chance); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_locking_time); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_signature_radius); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_fast_extraction); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_core_usage_gathering); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_siege); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_speed); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_repaired_amount); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_locking_range); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_ewar_optimal); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_armor_max); + _ = _builder.RegisterType().Keyed(EffectType.effect_aura_gang_shield_absorbtion_ratio); // intrusion effects - _builder.RegisterType().Keyed(EffectType.effect_intrusion_geoscan_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_geoscan_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_geoscan_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_mining_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_mining_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_mining_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_harvester_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_harvester_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_harvester_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_detection_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_detection_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_detection_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_masking_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_masking_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_masking_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_repair_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_repair_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_repair_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_core_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_core_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_core_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_signals_lvl4_combined); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_industrial_lvl4_combined); - _builder.RegisterType().Keyed(EffectType.effect_intrusion_engineering_lvl4_combined); - - _builder.RegisterType().Keyed(EffectType.effect_pbs_mining_tower_gammaterial_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_mining_tower_gammaterial_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_mining_tower_gammaterial_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_gap_generator_masking_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_gap_generator_masking_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_gap_generator_masking_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_engineering_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_engineering_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_engineering_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_industry_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_industry_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_industry_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_sensors_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_sensors_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_sensors_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_cycle_time_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_cycle_time_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_cycle_time_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_resist_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_resist_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_resist_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl1); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl2); - _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl3); - - _builder.Register(x => - { - var ctx = x.Resolve(); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_geoscan_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_geoscan_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_geoscan_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_mining_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_mining_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_mining_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_harvester_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_harvester_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_harvester_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_detection_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_detection_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_detection_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_masking_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_masking_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_masking_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_repair_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_repair_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_repair_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_core_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_core_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_core_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_signals_lvl4_combined); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_industrial_lvl4_combined); + _ = _builder.RegisterType().Keyed(EffectType.effect_intrusion_engineering_lvl4_combined); + + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_mining_tower_gammaterial_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_mining_tower_gammaterial_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_mining_tower_gammaterial_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_gap_generator_masking_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_gap_generator_masking_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_gap_generator_masking_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_engineering_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_engineering_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_engineering_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_industry_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_industry_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_industry_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_sensors_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_sensors_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_sensors_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_cycle_time_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_cycle_time_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_cycle_time_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_resist_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_resist_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_resist_lvl3); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl1); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl2); + _ = _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl3); + + // New Bonuses - OPP + _ = _builder.RegisterType().Keyed(EffectType.effect_beta_bonus); + _ = _builder.RegisterType().Keyed(EffectType.effect_beta2_bonus); + _ = _builder.RegisterType().Keyed(EffectType.effect_alpha_bonus); + _ = _builder.RegisterType().Keyed(EffectType.effect_alpha2_bonus); + + _ = _builder.Register(x => + { + IComponentContext ctx = x.Resolve(); return effectType => { - if (!ctx.IsRegisteredWithKey(effectType)) - return new Effect(); - - return ctx.ResolveKeyed(effectType); + return !ctx.IsRegisteredWithKey(effectType) ? new Effect() : ctx.ResolveKeyed(effectType); }; }); } public void InitItems() { - _builder.RegisterType(); - _builder.RegisterType().AsSelf().OnActivated(e => e.Instance.Init()).SingleInstance(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().AsSelf().OnActivated(e => e.Instance.Init()).SingleInstance(); } private IRegistrationBuilder RegisterEntity() where T : Entity @@ -742,12 +777,12 @@ private IRegistrationBuilder().OnActivated(e => { - e.Instance.SetReinforceHandler(e.Context.Resolve>(new TypedParameter(typeof(PBSObject),e.Instance))); - e.Instance.SetPBSObjectHelper(e.Context.Resolve>(new TypedParameter(typeof(PBSObject),e.Instance))); + e.Instance.SetReinforceHandler(e.Context.Resolve>(new TypedParameter(typeof(PBSObject), e.Instance))); + e.Instance.SetPBSObjectHelper(e.Context.Resolve>(new TypedParameter(typeof(PBSObject), e.Instance))); }); } - private IRegistrationBuilder RegisterPBSProductionFacilityNode() where T : PBSProductionFacilityNode + private IRegistrationBuilder RegisterPBSProductionFacilityNode() where T : PBSProductionFacilityNode { return RegisterPBSObject().OnActivated(e => { @@ -758,13 +793,13 @@ private IRegistrationBuilder() where T : Corporation { - _builder.RegisterType(); - RegisterEntity().PropertiesAutowired(); + _ = _builder.RegisterType(); + _ = RegisterEntity().PropertiesAutowired(); } - protected void RegisterProximityProbe() where T : ProximityProbeBase + protected void RegisterProximityDevices() where T : ProximityDeviceBase { - RegisterUnit(); + _ = RegisterUnit(); } private IRegistrationBuilder RegisterModule() where T : Module @@ -774,12 +809,12 @@ private IRegistrationBuilder() where T : EffectModule { - RegisterModule(); + _ = RegisterModule(); } private void RegisterProductionFacility() where T : ProductionFacility { - RegisterEntity().PropertiesAutowired(); + _ = RegisterEntity().PropertiesAutowired(); } public IRegistrationBuilder RegisterRobot() where T : Robot @@ -808,12 +843,15 @@ private void RegisterEntities() RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); + RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); + RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); + RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); RegisterRobot(); RegisterRobot(); - + _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); - + RegisterEntity(); RegisterCorporation(); RegisterCorporation(); @@ -825,12 +863,13 @@ private void RegisterEntities() RegisterEntity(); RegisterUnit(); RegisterUnit(); + RegisterUnit(); RegisterUnit().OnActivated(e => { -#if (DEBUG) - var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromSeconds(10),TimeSpan.FromSeconds(15)); +#if DEBUG + TimeRange intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)); #else - var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromHours(8), TimeSpan.FromHours(8)); + TimeRange intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromHours(8), TimeSpan.FromHours(8)); #endif e.Instance.IntrusionWaitTime = intrusionWaitTime; }); @@ -841,6 +880,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity(); // OPP: new CT Capsule item RegisterProductionFacility(); RegisterProductionFacility(); @@ -868,7 +908,10 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); - RegisterProximityProbe(); + + RegisterProximityDevices(); + RegisterProximityDevices(); + RegisterUnit(); RegisterUnit().OnActivated(e => e.Instance.SetDespawnTime(TimeSpan.FromMinutes(15))); RegisterUnit().OnActivated(e => e.Instance.SetDespawnTime(TimeSpan.FromHours(1))); @@ -882,8 +925,11 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); + RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -897,7 +943,6 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); - RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -905,9 +950,12 @@ private void RegisterEntities() RegisterEntity(); RegisterModule(); + RegisterModule(); + RegisterModule(); RegisterModule(); RegisterModule(); RegisterModule(); + RegisterModule(); // OPP: new subclass for firearms RegisterModule(); RegisterModule(); RegisterModule(); @@ -922,6 +970,7 @@ private void RegisterEntities() RegisterModule(); RegisterModule(); RegisterModule(); + RegisterModule(); RegisterModule(); RegisterModule(); RegisterModule(); @@ -929,16 +978,16 @@ private void RegisterEntities() RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); + RegisterEffectModule(); //OPP: NPC-only module for detection debuff RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); + RegisterEffectModule(); RegisterEntity(); - RegisterEntity(); - RegisterEntity(); RegisterEntity(); RegisterUnit(); @@ -947,6 +996,7 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit(); RegisterUnit(); + RegisterUnit(); // OPP: New mobile tele for entry to Strongholds RegisterUnit(); RegisterUnit(); RegisterPBSObject(); @@ -973,14 +1023,17 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit(); RegisterUnit(); + RegisterUnit(); // OPP: Special rift spawned eventfully to transport player to location + RegisterUnit(); // OPP: Special rift for exiting strongholds - RegisterEntity(); RegisterEntity(); RegisterEntity(); - RegisterEntity(); + + RegisterEntity(); + RegisterEntity(); + RegisterEntity(); RegisterEntity(); - RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -993,32 +1046,50 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity(); // OPP: Robot paint item + RegisterEntity(); + RegisterEntity(); + RegisterEntity(); - _builder.Register>(x => + _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); - var b = new ContainerBuilder(); + ContainerBuilder b = new ContainerBuilder(); - void ByDefinition(int definition,params Parameter[] parameters) where T:Entity + void ByDefinition(int definition, params Parameter[] parameters) where T : Entity { b.Register(_ => ctx.Resolve(parameters)).Keyed(definition); } - void ByCategoryFlags(CategoryFlags cf,params Parameter[] parameters) where T : Entity + void ByCategoryFlags(CategoryFlags cf, params Parameter[] parameters) where T : Entity { - foreach (var entityDefault in ctx.Resolve().GetAll().GetByCategoryFlags(cf)) + foreach (EntityDefault entityDefault in ctx.Resolve().GetAll().GetByCategoryFlags(cf)) { - ByDefinition(entityDefault.Definition,parameters); + ByDefinition(entityDefault.Definition, parameters); } } - void ByName(string name,params Parameter[] parameters) where T : Entity + void ByName(string name, params Parameter[] parameters) where T : Entity { - var ed = ctx.Resolve().GetByName(name); + EntityDefault ed = ctx.Resolve().GetByName(name); ByDefinition(ed.Definition, parameters); } + //TODO: bit of a hack for using the same category for many items grouped by definitionname prefixes + //TODO: make separate category for new item groups! + void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] parameters) where T : Entity + { + //TODO: this might be expensive -- string matching all defaults + IEnumerable matches = ctx.Resolve().GetAll() + .Where(i => i.CategoryFlags == cf) + .Where(i => i.Name.Contains(substr)); + foreach (EntityDefault ed in matches) + { + ByDefinition(ed.Definition, parameters); + } + } + ByName(DefinitionNames.LOOT_CONTAINER_OBJECT); ByName(DefinitionNames.FIELD_CONTAINER); ByName(DefinitionNames.MISSION_CONTAINER); @@ -1044,6 +1115,12 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_projectile_ammo); ByCategoryFlags(CategoryFlags.cf_missile_ammo); ByCategoryFlags(CategoryFlags.cf_mining_ammo); + ByCategoryFlags(CategoryFlags.cf_sentry_turret_units); + ByCategoryFlags(CategoryFlags.cf_mining_turret_units); + ByCategoryFlags(CategoryFlags.cf_harvesting_turret_units); + ByCategoryFlags(CategoryFlags.cf_pelistal_combat_drones_units); + ByCategoryFlags(CategoryFlags.cf_nuimqol_combat_drones_units); + ByCategoryFlags(CategoryFlags.cf_thelodica_combat_drones_units); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_one_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_artifact); @@ -1060,6 +1137,7 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_public_corporation_hangar_storage); ByCategoryFlags(CategoryFlags.cf_public_docking_base); ByCategoryFlags(CategoryFlags.cf_pbs_docking_base); + ByName(DefinitionNames.PBS_EXPIRING_DOCKING_BASE); //OPP: new expiring base ByCategoryFlags(CategoryFlags.cf_outpost); ByCategoryFlags(CategoryFlags.cf_outpost_mill); ByCategoryFlags(CategoryFlags.cf_outpost_prototyper); @@ -1101,34 +1179,38 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_robot_equipment); - ByCategoryFlags(CategoryFlags.cf_small_lasers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_small_crystals)); - ByCategoryFlags(CategoryFlags.cf_medium_lasers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_medium_crystals)); - ByCategoryFlags(CategoryFlags.cf_large_lasers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_large_crystals)); - ByCategoryFlags(CategoryFlags.cf_small_railguns,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_small_railgun_ammo)); - ByCategoryFlags(CategoryFlags.cf_medium_railguns,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_medium_railgun_ammo)); - ByCategoryFlags(CategoryFlags.cf_large_railguns,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_large_railgun_ammo)); - ByCategoryFlags(CategoryFlags.cf_small_single_projectile,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_small_projectile_ammo)); - ByCategoryFlags(CategoryFlags.cf_medium_single_projectile,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_medium_projectile_ammo)); - ByCategoryFlags(CategoryFlags.cf_large_single_projectile,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_large_projectile_ammo)); - ByCategoryFlags(CategoryFlags.cf_small_missile_launchers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_small_missile_ammo)); - ByCategoryFlags(CategoryFlags.cf_medium_missile_launchers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_medium_missile_ammo)); - ByCategoryFlags(CategoryFlags.cf_large_missile_launchers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_large_missile_ammo)); + ByCategoryFlags(CategoryFlags.cf_small_lasers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_small_crystals)); + ByCategoryFlags(CategoryFlags.cf_medium_lasers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_medium_crystals)); + ByCategoryFlags(CategoryFlags.cf_large_lasers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_large_crystals)); + ByCategoryFlags(CategoryFlags.cf_small_railguns, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_small_railgun_ammo)); + ByCategoryFlags(CategoryFlags.cf_medium_railguns, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_medium_railgun_ammo)); + ByCategoryFlags(CategoryFlags.cf_large_railguns, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_large_railgun_ammo)); + ByCategoryFlags(CategoryFlags.cf_small_single_projectile, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_small_projectile_ammo)); + ByCategoryFlags(CategoryFlags.cf_medium_single_projectile, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_medium_projectile_ammo)); + ByCategoryFlags(CategoryFlags.cf_large_single_projectile, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_large_projectile_ammo)); + ByCategoryFlags(CategoryFlags.cf_sentry_turret_guns, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_large_projectile_ammo)); + ByCategoryFlags(CategoryFlags.cf_small_missile_launchers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_small_missile_ammo)); + ByCategoryFlags(CategoryFlags.cf_medium_missile_launchers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_medium_missile_ammo)); + ByCategoryFlags(CategoryFlags.cf_large_missile_launchers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_large_missile_ammo)); ByCategoryFlags(CategoryFlags.cf_shield_generators); ByCategoryFlags(CategoryFlags.cf_armor_repair_systems); ByCategoryFlags(CategoryFlags.cf_remote_armor_repairers); - ByCategoryFlags(CategoryFlags.cf_core_boosters,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_core_booster_ammo)); + ByCategoryFlags(CategoryFlags.cf_core_boosters, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_core_booster_ammo)); ByCategoryFlags(CategoryFlags.cf_sensor_jammers); ByCategoryFlags(CategoryFlags.cf_energy_neutralizers); ByCategoryFlags(CategoryFlags.cf_energy_transferers); ByCategoryFlags(CategoryFlags.cf_energy_vampires); - ByCategoryFlags(CategoryFlags.cf_drillers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_mining_ammo)); - ByCategoryFlags(CategoryFlags.cf_harvesters,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_harvesting_ammo)); - ByCategoryFlags(CategoryFlags.cf_mining_probes,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_mining_probe_ammo)); + ByCategoryFlags(CategoryFlags.cf_drillers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_mining_ammo)); + ByCategoryFlags(CategoryFlags.cf_industrial_turret_drillers); + ByCategoryFlags(CategoryFlags.cf_industrial_turret_harvesters); + ByCategoryFlags(CategoryFlags.cf_harvesters, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_harvesting_ammo)); + ByCategoryFlags(CategoryFlags.cf_mining_probes, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_mining_probe_ammo)); ByCategoryFlags(CategoryFlags.cf_chassis_scanner); ByCategoryFlags(CategoryFlags.cf_cargo_scanner); ByCategoryFlags(CategoryFlags.cf_siege_hack_modules); ByCategoryFlags(CategoryFlags.cf_neuralyzer); - ByCategoryFlags(CategoryFlags.cf_blob_emission_modulator,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_blob_emission_modulator_ammo)); + ByCategoryFlags(CategoryFlags.cf_blob_emission_modulator, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_blob_emission_modulator_ammo)); + ByCategoryFlags(CategoryFlags.cf_remote_controllers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_remote_controlled_units)); ByCategoryFlags(CategoryFlags.cf_webber); ByCategoryFlags(CategoryFlags.cf_sensor_dampeners); ByCategoryFlags(CategoryFlags.cf_remote_sensor_boosters); @@ -1137,6 +1219,7 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_armor_hardeners); ByCategoryFlags(CategoryFlags.cf_stealth_modules); ByCategoryFlags(CategoryFlags.cf_detection_modules); + ByCategoryFlags(CategoryFlags.cf_landmine_detectors); ByCategoryFlags(CategoryFlags.cf_armor_plates); ByCategoryFlags(CategoryFlags.cf_core_batteries); ByCategoryFlags(CategoryFlags.cf_core_rechargers); @@ -1153,19 +1236,19 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_eccm); ByCategoryFlags(CategoryFlags.cf_resistance_plating); - ByCategoryFlags(CategoryFlags.cf_gang_assist_speed,new NamedParameter("effectType",EffectType.effect_aura_gang_speed),new NamedParameter("effectModifier",AggregateField.effect_speed_max_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_defense,new NamedParameter("effectType",EffectType.effect_aura_gang_armor_max),new NamedParameter("effectModifier",AggregateField.effect_armor_max_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_information,new NamedParameter("effectType",EffectType.effect_aura_gang_locking_range),new NamedParameter("effectModifier",AggregateField.effect_locking_range_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_industry,new NamedParameter("effectType",EffectType.effect_aura_gang_core_usage_gathering),new NamedParameter("effectModifier",AggregateField.effect_core_usage_gathering_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_shared_dataprocessing,new NamedParameter("effectType",EffectType.effect_aura_gang_locking_time),new NamedParameter("effectModifier",AggregateField.effect_locking_time_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_coordinated_manuevering,new NamedParameter("effectType",EffectType.effect_aura_gang_signature_radius),new NamedParameter("effectModifier",AggregateField.effect_signature_radius_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_maintance,new NamedParameter("effectType",EffectType.effect_aura_gang_repaired_amount),new NamedParameter("effectModifier",AggregateField.effect_repair_amount_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_precision_firing,new NamedParameter("effectType",EffectType.effect_aura_gang_critical_hit_chance),new NamedParameter("effectModifier",AggregateField.effect_critical_hit_chance_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_core_management,new NamedParameter("effectType",EffectType.effect_aura_gang_core_recharge_time),new NamedParameter("effectModifier",AggregateField.effect_core_recharge_time_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_shield_calculations,new NamedParameter("effectType",EffectType.effect_aura_gang_shield_absorbtion_ratio),new NamedParameter("effectModifier",AggregateField.effect_shield_absorbtion_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_siege,new NamedParameter("effectType",EffectType.effect_aura_gang_siege),new NamedParameter("effectModifier",AggregateField.effect_weapon_cycle_time_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_ewar,new NamedParameter("effectType",EffectType.effect_aura_gang_ewar_optimal),new NamedParameter("effectModifier",AggregateField.effect_ew_optimal_range_modifier)); - ByCategoryFlags(CategoryFlags.cf_gang_assist_fast_extracting,new NamedParameter("effectType",EffectType.effect_aura_gang_fast_extraction),new NamedParameter("effectModifier",AggregateField.effect_gathering_cycle_time_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_speed, new NamedParameter("effectType", EffectType.effect_aura_gang_speed), new NamedParameter("effectModifier", AggregateField.effect_speed_max_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_defense, new NamedParameter("effectType", EffectType.effect_aura_gang_armor_max), new NamedParameter("effectModifier", AggregateField.effect_armor_max_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_information, new NamedParameter("effectType", EffectType.effect_aura_gang_locking_range), new NamedParameter("effectModifier", AggregateField.effect_locking_range_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_industry, new NamedParameter("effectType", EffectType.effect_aura_gang_core_usage_gathering), new NamedParameter("effectModifier", AggregateField.effect_core_usage_gathering_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_shared_dataprocessing, new NamedParameter("effectType", EffectType.effect_aura_gang_locking_time), new NamedParameter("effectModifier", AggregateField.effect_locking_time_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_coordinated_manuevering, new NamedParameter("effectType", EffectType.effect_aura_gang_signature_radius), new NamedParameter("effectModifier", AggregateField.effect_signature_radius_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_maintance, new NamedParameter("effectType", EffectType.effect_aura_gang_repaired_amount), new NamedParameter("effectModifier", AggregateField.effect_repair_amount_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_precision_firing, new NamedParameter("effectType", EffectType.effect_aura_gang_critical_hit_chance), new NamedParameter("effectModifier", AggregateField.effect_critical_hit_chance_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_core_management, new NamedParameter("effectType", EffectType.effect_aura_gang_core_recharge_time), new NamedParameter("effectModifier", AggregateField.effect_core_recharge_time_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_shield_calculations, new NamedParameter("effectType", EffectType.effect_aura_gang_shield_absorbtion_ratio), new NamedParameter("effectModifier", AggregateField.effect_shield_absorbtion_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_siege, new NamedParameter("effectType", EffectType.effect_aura_gang_siege), new NamedParameter("effectModifier", AggregateField.effect_weapon_cycle_time_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_ewar, new NamedParameter("effectType", EffectType.effect_aura_gang_ewar_optimal), new NamedParameter("effectModifier", AggregateField.effect_ew_optimal_range_modifier)); + ByCategoryFlags(CategoryFlags.cf_gang_assist_fast_extracting, new NamedParameter("effectType", EffectType.effect_aura_gang_fast_extraction), new NamedParameter("effectModifier", AggregateField.effect_gathering_cycle_time_modifier)); ByCategoryFlags(CategoryFlags.cf_logical_storage); @@ -1173,20 +1256,41 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_robotshards); ByCategoryFlags(CategoryFlags.cf_others); ByCategoryFlags(CategoryFlags.cf_blob_emitter); + ByCategoryFlags(CategoryFlags.cf_sentry_turrets); + ByCategoryFlags(CategoryFlags.cf_mining_turrets); + ByCategoryFlags(CategoryFlags.cf_harvesting_turrets); + ByCategoryFlags(CategoryFlags.cf_combat_drones); ByCategoryFlags(CategoryFlags.cf_reactor_cores); ByCategoryFlags(CategoryFlags.cf_kiosk); ByCategoryFlags(CategoryFlags.cf_alarm_switch); ByCategoryFlags(CategoryFlags.cf_simple_switch); ByCategoryFlags(CategoryFlags.cf_item_supply); ByCategoryFlags(CategoryFlags.cf_mobile_world_teleport); + ByNamePatternAndFlag("def_mobile_teleport_stronghold", CategoryFlags.cf_mobile_world_teleport); // OPP: stronghold tele ByCategoryFlags(CategoryFlags.cf_mission_coin); ByCategoryFlags(CategoryFlags.cf_area_bomb); ByCategoryFlags(CategoryFlags.cf_plasma_bomb); - ByCategoryFlags(CategoryFlags.cf_visibility_based_probe); + + ByCategoryFlags(CategoryFlags.cf_proximity_probe); + ByCategoryFlags(CategoryFlags.cf_light_landmines); + ByCategoryFlags(CategoryFlags.cf_medium_landmines); + ByCategoryFlags(CategoryFlags.cf_heavy_landmines); + ByCategoryFlags(CategoryFlags.cf_random_research_kits); ByCategoryFlags(CategoryFlags.cf_lottery_items); + ByCategoryFlags(CategoryFlags.cf_paints); // OPP Robot paint! + ByCategoryFlags(CategoryFlags.cf_ct_capsules); // OPP CT capsules + ByCategoryFlags(CategoryFlags.cf_ep_boosters); // OPP EP Boosters + ByCategoryFlags(CategoryFlags.cf_datashards); // OPP datashards + ByCategoryFlags(CategoryFlags.cf_respec_tokens); // OPP respec tokens + ByCategoryFlags(CategoryFlags.cf_spark_teleport_devices); + + // OPP new Blinder module + ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); + + ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); + ByCategoryFlags(CategoryFlags.cf_landmine_deployer); - ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); ByCategoryFlags(CategoryFlags.cf_pbs_capsules); ByCategoryFlags(CategoryFlags.cf_pbs_egg); @@ -1230,36 +1334,31 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByName(DefinitionNames.ADMIN_TRASHCAN); ByName(DefinitionNames.ZONE_STORAGE); ByName(DefinitionNames.DEPLOY_PUNCHBAG); - ByName(DefinitionNames.TERRAFORM_MULTI_MODULE,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_ammo_terraforming_multi)); - ByName(DefinitionNames.STANDARD_WALL_BUILDER,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_wall_builder_ammo)); + ByName(DefinitionNames.TERRAFORM_MULTI_MODULE, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_ammo_terraforming_multi)); + ByName(DefinitionNames.STANDARD_WALL_BUILDER, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_wall_builder_ammo)); ByName(DefinitionNames.PBS_FACILITY_MILL); ByName(DefinitionNames.PBS_FACILITY_PROTOTYPER); ByName(DefinitionNames.PBS_FACILITY_REFINERY); ByName(DefinitionNames.PBS_FACILITY_REPAIR); ByName(DefinitionNames.PBS_FACILITY_REPROCESSOR); ByName(DefinitionNames.PBS_FACILITY_RESEARCH_LAB); - ByName(DefinitionNames.PBS_CONSTRUCTION_MODULE,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_construction_ammo)); + ByName(DefinitionNames.PBS_CONSTRUCTION_MODULE, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_construction_ammo)); ByName(DefinitionNames.PLANT_SEED_DEVRINOL); ByName(DefinitionNames.GATE); ByName(DefinitionNames.GATE_CAPSULE); ByName(DefinitionNames.RANDOM_RIFT_PORTAL); ByName(DefinitionNames.BASE_ITEM_SHOP); ByName(DefinitionNames.ANNIVERSARY_PACKAGE); + ByName(DefinitionNames.STRONGHOLD_EXIT_RIFT); //OPP stronghold static exit rift + ByName(DefinitionNames.TARGETTED_RIFT); //OPP targetted rift + ByName(DefinitionNames.RELIC); //OPP Relic + ByName(DefinitionNames.RELIC_SAP); //OPP outpost Relic - var c = b.Build(); + IContainer c = b.Build(); return ed => { - Entity entity; - if (!c.IsRegisteredWithKey(ed.Definition)) - { - entity = ctx.Resolve(); - } - else - { - entity = c.ResolveKeyed(ed.Definition); - } - + Entity entity = !c.IsRegisteredWithKey(ed.Definition) ? ctx.Resolve() : c.ResolveKeyed(ed.Definition); entity.ED = ed; entity.Health = ed.Health; entity.Quantity = ed.Quantity; @@ -1271,7 +1370,7 @@ void ByName(string name,params Parameter[] parameters) where T : Entity private void RegisterLoggers() { - _builder.Register(x => + _ = _builder.Register(x => { return new LoggerCache(new MemoryCache("LoggerCache")) { @@ -1279,88 +1378,91 @@ private void RegisterLoggers() }; }).As().SingleInstance(); - _builder.Register(x => + _ = _builder.Register(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return name => { - var fileLogger = ctx.Resolve>>().Invoke("channels", name); + FileLogger fileLogger = ctx.Resolve>>().Invoke("channels", name); return new ChannelLogger(fileLogger); }; }); - _builder.RegisterGeneric(typeof(FileLogger<>)); + _ = _builder.RegisterGeneric(typeof(FileLogger<>)); - _builder.Register>>(x => + _ = _builder.Register>>(x => { - var ctx = x.Resolve(); - return ((directory, filename) => + IComponentContext ctx = x.Resolve(); + return (directory, filename) => { - var formatter = new ChatLogFormatter(); - return ctx.Resolve.Factory>().Invoke(formatter,() => Path.Combine("chatlogs",directory,filename,DateTime.Now.ToString("yyyy-MM-dd"), $"{filename.RemoveSpecialCharacters()}.txt")); - }); + ChatLogFormatter formatter = new ChatLogFormatter(); + return ctx.Resolve.Factory>().Invoke(formatter, () => Path.Combine("chatlogs", directory, filename, DateTime.Now.ToString("yyyy-MM-dd"), $"{filename.RemoveSpecialCharacters()}.txt")); + }; }); - _builder.Register(x => + _ = _builder.Register(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return (directory, filename) => { - var fileLogger = ctx.Resolve>>().Invoke(directory, filename); + FileLogger fileLogger = ctx.Resolve>>().Invoke(directory, filename); return fileLogger; }; }); - _builder.Register(c => + _ = _builder.Register(c => { - var defaultFormater = new DefaultLogEventFormatter(); + DefaultLogEventFormatter defaultFormater = new DefaultLogEventFormatter(); - var formater = new DelegateLogEventFormatter(e => + DelegateLogEventFormatter formater = new DelegateLogEventFormatter(e => { - var formatedEvent = defaultFormater.Format(e); + string formatedEvent = defaultFormater.Format(e); - var gex = e.ThrownException as PerpetuumException; - if (gex == null) + if (!(e.ThrownException is PerpetuumException gex)) + { return formatedEvent; + } - var sb = new StringBuilder(formatedEvent); + StringBuilder sb = new StringBuilder(formatedEvent); - sb.AppendLine(); - sb.AppendLine(); - sb.AppendFormat("Error = {0}\n", gex.error); + _ = sb.AppendLine(); + _ = sb.AppendLine(); + _ = sb.AppendFormat("Error = {0}\n", gex.error); if (gex.Data.Count > 0) - sb.AppendFormat("Data: {0}", gex.Data.ToDictionary().ToDebugString()); + { + _ = sb.AppendFormat("Data: {0}", gex.Data.ToDictionary().ToDebugString()); + } return sb.ToString(); }); - var fileLogger = c.Resolve.Factory>().Invoke(formater, () => Path.Combine("logs", DateTime.Now.ToString("yyyy-MM-dd"), "hostlog.txt")); + FileLogger fileLogger = c.Resolve.Factory>().Invoke(formater, () => Path.Combine("logs", DateTime.Now.ToString("yyyy-MM-dd"), "hostlog.txt")); fileLogger.BufferSize = 100; fileLogger.AutoFlushInterval = TimeSpan.FromSeconds(10); return new CompositeLogger(fileLogger, new ColoredConsoleLogger(formater)); }).As>(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); - _builder.RegisterGeneric(typeof(DbLogger<>)); + _ = _builder.RegisterGeneric(typeof(DbLogger<>)); } - private IRegistrationBuilder - RegisterPresence(PresenceType presenceType) where T:Presence + private IRegistrationBuilder + RegisterPresence(PresenceType presenceType) where T : Presence { return _builder.RegisterType().Keyed(presenceType).PropertiesAutowired(); } private void RegisterFlock(PresenceType presenceType) where T : Flock { - _builder.RegisterType().Keyed(presenceType).OnActivated(e => + _ = _builder.RegisterType().Keyed(presenceType).OnActivated(e => { e.Instance.EntityService = e.Context.Resolve(); e.Instance.LootService = e.Context.Resolve(); @@ -1369,103 +1471,126 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType().Named>("directFlockIDGenerator").SingleInstance().WithParameter("startID",25000); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType().SingleInstance().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().Named>("directFlockIDGenerator").SingleInstance().WithParameter("startID", 25000); - _builder.RegisterType().OnActivated(e => + + _ = _builder.RegisterType().OnActivated(e => { e.Instance.LoadAllConfig(); }).As().SingleInstance(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType() + _ = _builder.RegisterType() .As() .SingleInstance() .OnActivated(e => e.Instance.Init()); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().As().SingleInstance(); + + _ = _builder.RegisterType() + .As() + .SingleInstance() + .OnActivated(e => e.Instance.Init()); + + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().OnActivated(e => { - var pm = e.Context.Resolve(); + IProcessManager pm = e.Context.Resolve(); pm.AddProcess(e.Instance.AsTimed(TimeSpan.FromSeconds(2)).ToAsync()); e.Instance.LoadAll(); }).As(); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { - var presenceFactory = ctx.Resolve(); - var presenceService = ctx.Resolve().Invoke(zone,presenceFactory); + PresenceFactory presenceFactory = ctx.Resolve(); + IPresenceManager presenceService = ctx.Resolve().Invoke(zone, presenceFactory); return presenceService; }; }); - _builder.Register(x => + _ = _builder.Register(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); - return ((configuration, presence) => + return (configuration, presence) => { - return ctx.ResolveKeyed(presence.Configuration.presenceType, TypedParameter.From(configuration), TypedParameter.From(presence)); - }); + return ctx.ResolveKeyed(presence.Configuration.PresenceType, TypedParameter.From(configuration), TypedParameter.From(presence)); + }; }); RegisterFlock(PresenceType.Normal); RegisterFlock(PresenceType.Direct); RegisterFlock(PresenceType.DynamicPool); RegisterFlock(PresenceType.Dynamic); + RegisterFlock(PresenceType.DynamicExtended); + RegisterFlock(PresenceType.ExpiringRandom); RegisterFlock(PresenceType.Random); - RegisterFlock(PresenceType.Roaming); - RegisterFlock(PresenceType.FreeRoaming); + RegisterFlock(PresenceType.Roaming); + RegisterFlock(PresenceType.FreeRoaming); + RegisterFlock(PresenceType.Interzone); + RegisterFlock(PresenceType.InterzoneRoaming); + RegisterFlock(PresenceType.EscalatingRandomPresence); + RegisterFlock(PresenceType.GrowingNPCBasePresence); - RegisterPresence(PresenceType.Normal); - RegisterPresence(PresenceType.Direct).OnActivated(e => + _ = RegisterPresence(PresenceType.Normal); + _ = RegisterPresence(PresenceType.Direct).OnActivated(e => { e.Instance.FlockIDGenerator = e.Context.ResolveNamed>("directFlockIDGenerator"); }); - RegisterPresence(PresenceType.DynamicPool); - RegisterPresence(PresenceType.Dynamic); - RegisterPresence(PresenceType.Random); - RegisterPresence(PresenceType.Roaming); - RegisterPresence(PresenceType.FreeRoaming); - - _builder.Register(x => - { - var ctx = x.Resolve(); - return ((zone, configuration) => + _ = RegisterPresence(PresenceType.DynamicPool); + _ = RegisterPresence(PresenceType.Dynamic); + _ = RegisterPresence(PresenceType.DynamicExtended); + _ = RegisterPresence(PresenceType.ExpiringRandom); + _ = RegisterPresence(PresenceType.Random); + _ = RegisterPresence(PresenceType.Roaming); + _ = RegisterPresence(PresenceType.FreeRoaming); + _ = RegisterPresence(PresenceType.Interzone); + _ = RegisterPresence(PresenceType.InterzoneRoaming); + _ = RegisterPresence(PresenceType.EscalatingRandomPresence); + _ = RegisterPresence(PresenceType.GrowingNPCBasePresence); + + _ = _builder.Register(x => + { + IComponentContext ctx = x.Resolve(); + return (zone, configuration) => { - if (!ctx.IsRegisteredWithKey(configuration.presenceType)) + if (!ctx.IsRegisteredWithKey(configuration.PresenceType)) + { return null; + } - var p = ctx.ResolveKeyed(configuration.presenceType,TypedParameter.From(zone),TypedParameter.From(configuration)); + Presence p = ctx.ResolveKeyed(configuration.PresenceType, TypedParameter.From(zone), TypedParameter.From(configuration)); - if (p is RoamingPresence roamingPresence) + if (p is IRoamingPresence roamingPresence) { - switch (p.Configuration.presenceType) + switch (p.Configuration.PresenceType) { case PresenceType.Roaming: - { roamingPresence.PathFinder = new NormalRoamingPathFinder(zone); break; - } - case PresenceType.FreeRoaming: - { + default: roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); break; - } } } return p; - }); + }; }); @@ -1473,160 +1598,193 @@ public void RegisterNpcs() private void RegisterMtProducts() { - _builder.RegisterType().As(); - _builder.RegisterType(); - RegisterRequestHandler(Commands.MtProductPriceList); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = RegisterRequestHandler(Commands.MtProductPriceList); } private void RegisterMissions() { - _builder.RegisterType(); - _builder.RegisterType().SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().OnActivated(e => - { - var pm = e.Context.Resolve(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().OnActivated(e => + { + IProcessManager pm = e.Context.Resolve(); pm.AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(1))); }).SingleInstance(); - RegisterRequestHandler(Commands.MissionData); - RegisterRequestHandler(Commands.MissionStart); - RegisterRequestHandler(Commands.MissionAbort); - RegisterRequestHandler(Commands.MissionAdminListAll); - RegisterRequestHandler(Commands.MissionAdminTake); - RegisterRequestHandler(Commands.MissionLogList); - RegisterRequestHandler(Commands.MissionListRunning); - RegisterRequestHandler(Commands.MissionReloadCache); - RegisterRequestHandler(Commands.MissionGetOptions); - RegisterRequestHandler(Commands.MissionResolveTest); - RegisterRequestHandler(Commands.MissionDeliver); - RegisterRequestHandler(Commands.MissionFlush); - RegisterRequestHandler(Commands.MissionReset); - RegisterRequestHandler(Commands.MissionListAgents); - - _builder.RegisterType(); - _builder.RegisterType(); + _ = RegisterRequestHandler(Commands.MissionData); + _ = RegisterRequestHandler(Commands.MissionStart); + _ = RegisterRequestHandler(Commands.MissionAbort); + _ = RegisterRequestHandler(Commands.MissionAdminListAll); + _ = RegisterRequestHandler(Commands.MissionAdminTake); + _ = RegisterRequestHandler(Commands.MissionLogList); + _ = RegisterRequestHandler(Commands.MissionListRunning); + _ = RegisterRequestHandler(Commands.MissionReloadCache); + _ = RegisterRequestHandler(Commands.MissionGetOptions); + _ = RegisterRequestHandler(Commands.MissionResolveTest); + _ = RegisterRequestHandler(Commands.MissionDeliver); + _ = RegisterRequestHandler(Commands.MissionFlush); + _ = RegisterRequestHandler(Commands.MissionReset); + _ = RegisterRequestHandler(Commands.MissionListAgents); + + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); } private void InitRelayManager() { - _builder.RegisterType().SingleInstance().OnActivated(e => e.Instance.Init()); - _builder.RegisterType().OnActivated(e => - { - e.Instance.Init(); - }).SingleInstance(); + _ = _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType().SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.Register(c => new MarketInfoService(0.3, 10, false)).As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.Register(c => new MarketInfoService(0.3, 10, false)).As(); - _builder.RegisterType().As().OnActivated(e => + _ = _builder.RegisterType().As().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromHours(4))); }); - _builder.RegisterType().As().SingleInstance().OnActivated(e => + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(2))); }); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance().OnActivated(e => + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(1))); }); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().OnActivated(e => { e.Instance.Init(); }).As().SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().SingleInstance().OnActivated(e => + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().SingleInstance().OnActivated(e => { e.Instance.InitProcessor(); }); - _builder.RegisterType().SingleInstance().OnActivated(e => + _ = _builder.RegisterType().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(1))); }); - _builder.RegisterType().As().SingleInstance().OnActivated(e => + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(5))); }); - _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().SingleInstance().As(); - _builder.RegisterType(); + _ = _builder.RegisterType().SingleInstance(); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().OnActivated(e => { - var pm = e.Context.Resolve(); + IProcessManager pm = e.Context.Resolve(); pm.AddProcess(e.Instance.AsTimed(TimeSpan.FromMilliseconds(200)).ToAsync()); }); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + + + _ = _builder.RegisterType(); + + _ = _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType(); - - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType(); + // OPP: EPBonusEventService singleton + _ = _builder.RegisterType().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1))); + }); + // OPP: EventListenerService and consumers + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As>(); + _ = _builder.RegisterType().As>(); + _ = _builder.RegisterType().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(0.75))); + e.Instance.AttachListener(e.Context.Resolve()); + e.Instance.AttachListener(e.Context.Resolve()); + e.Instance.AttachListener(e.Context.Resolve()); + e.Instance.AttachListener(e.Context.Resolve()); + e.Instance.AttachListener(e.Context.Resolve()); + GameTimeObserver obs = new GameTimeObserver(e.Instance); + obs.Subscribe(e.Context.Resolve()); + }); - _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1.5))); + }); - _builder.RegisterType(); + // OPP: InterzoneNPCManager + _ = RegisterAutoActivate(TimeSpan.FromSeconds(10)); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType().AsSelf().As(); - _builder.Register(x => + _ = _builder.RegisterType(); + _ = _builder.RegisterType().AsSelf().As(); + _ = _builder.Register(x => { - var ctx = x.Resolve(); - return ((account, type) => + IComponentContext ctx = x.Resolve(); + return (account, type) => { return ctx.Resolve(new TypedParameter(typeof(Account), account), new TypedParameter(typeof(AccountTransactionType), type)); - }); + }; }); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); } - private IRegistrationBuilder - RegisterRequestHandler(Command command) where TRequestHandler:IRequestHandler where TRequest : IRequest + private IRegistrationBuilder + RegisterRequestHandler(Command command) where TRequestHandler : IRequestHandler where TRequest : IRequest { - var res = _builder.RegisterType(); + IRegistrationBuilder res = _builder.RegisterType(); - _builder.Register(c => + _ = _builder.Register(c => { return c.Resolve>(new TypedParameter(typeof(IRequestHandler), c.Resolve())); }).Keyed>(command); @@ -1641,539 +1799,554 @@ private IRegistrationBuilder() where T : IRequest { - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); - return (command => + IComponentContext ctx = x.Resolve(); + return command => { return ctx.IsRegisteredWithKey>(command) ? ctx.ResolveKeyed>(command) : null; - }); + }; }); } private void RegisterRequestHandlers() { - _builder.RegisterGeneric(typeof(RequestHandlerProfiler<>)); + _ = _builder.RegisterGeneric(typeof(RequestHandlerProfiler<>)); RegisterRequestHandlerFactory(); RegisterRequestHandlerFactory(); - RegisterRequestHandler(Commands.GetEnums); - RegisterRequestHandler(Commands.GetEntityDefaults).SingleInstance(); - RegisterRequestHandler(Commands.GetAggregateFields).SingleInstance(); - RegisterRequestHandler(Commands.GetDefinitionConfigUnits).SingleInstance(); - RegisterRequestHandler(Commands.GetEffects).SingleInstance(); - RegisterRequestHandler(Commands.GetDistances); - RegisterRequestHandler(Commands.SignIn); - RegisterRequestHandler(Commands.SignInSteam); - RegisterRequestHandler(Commands.SignOut); - RegisterRequestHandler(Commands.SteamListAccounts); - RegisterRequestHandler(Commands.CharacterList); - RegisterRequestHandler(Commands.CharacterCreate); - RegisterRequestHandler(Commands.CharacterSelect); - RegisterRequestHandler(Commands.CharacterDeselect); - RegisterRequestHandler(Commands.CharacterForceDeselect); - RegisterRequestHandler(Commands.CharacterForceDisconnect); - RegisterRequestHandler(Commands.CharacterDelete); - RegisterRequestHandler(Commands.CharacterSetHomeBase); - RegisterRequestHandler(Commands.CharacterGetProfiles); - RegisterRequestHandler(Commands.CharacterRename); - RegisterRequestHandler(Commands.CharacterCheckNick); - RegisterRequestHandler(Commands.CharacterCorrectNick); - RegisterRequestHandler(Commands.IsOnline); - RegisterRequestHandler(Commands.CharacterSettingsSet); - RegisterRequestHandler(Commands.CharacterSetMoodMessage); - RegisterRequestHandler(Commands.CharacterTransferCredit); - RegisterRequestHandler(Commands.CharacterSetAvatar); - RegisterRequestHandler(Commands.CharacterSetBlockTrades); - RegisterRequestHandler(Commands.CharacterSetCredit); - RegisterRequestHandler(Commands.CharacterClearHomeBase); - RegisterRequestHandler(Commands.CharacterSettingsGet); - RegisterRequestHandler(Commands.CharacterGetMyProfile); - RegisterRequestHandler(Commands.CharacterSearch); - RegisterRequestHandler(Commands.CharacterRemoveFromCache); - RegisterRequestHandler(Commands.CharacterListNpcDeath); - RegisterRequestHandler(Commands.CharacterTransactionHistory); - RegisterRequestHandler(Commands.CharacterGetZoneInfo); - RegisterRequestHandler(Commands.CharacterNickHistory); - RegisterRequestHandler(Commands.CharacterGetNote); - RegisterRequestHandler(Commands.CharacterSetNote); - RegisterRequestHandler(Commands.CharacterCorporationHistory); - RegisterRequestHandler(Commands.CharacterWizardData).SingleInstance(); - RegisterRequestHandler(Commands.Chat); - RegisterRequestHandler(Commands.GoodiePackList); - RegisterRequestHandler(Commands.GoodiePackRedeem); - RegisterRequestHandler(Commands.Ping); - RegisterRequestHandler(Commands.Quit); - RegisterRequestHandler(Commands.SetMaxUserCount); - RegisterRequestHandler(Commands.SparkTeleportSet); - RegisterRequestHandler(Commands.SparkTeleportUse); - RegisterRequestHandler(Commands.SparkTeleportDelete); - RegisterRequestHandler(Commands.SparkTeleportList); - RegisterRequestHandler(Commands.SparkChange); - RegisterRequestHandler(Commands.SparkRemove); - RegisterRequestHandler(Commands.SparkList); - RegisterRequestHandler(Commands.SparkSetDefault); - RegisterRequestHandler(Commands.SparkUnlock); - RegisterRequestHandler(Commands.Undock); - RegisterRequestHandler(Commands.ProximityProbeRegisterSet); - RegisterRequestHandler(Commands.ProximityProbeSetName); - RegisterRequestHandler(Commands.ProximityProbeList); - RegisterRequestHandler(Commands.ProximityProbeGetRegistrationInfo); - RegisterRequestHandler(Commands.IntrusionEnabler); - RegisterRequestHandler(Commands.AccountGetTransactionHistory); - RegisterRequestHandler(Commands.AccountList); - - RegisterRequestHandler(Commands.AccountEpForActivityHistory); - RegisterRequestHandler(Commands.RedeemableItemList); - RegisterRequestHandler(Commands.RedeemableItemRedeem); - RegisterRequestHandler(Commands.RedeemableItemActivate); - RegisterRequestHandler(Commands.CreateItem); - RegisterRequestHandler(Commands.TeleportList); - RegisterRequestHandler(Commands.TeleportConnectColumns); - RegisterRequestHandler(Commands.EnableSelfTeleport); - RegisterRequestHandler(Commands.ItemCount); - RegisterRequestHandler(Commands.SystemInfo); - RegisterRequestHandler(Commands.TransferData); - - RegisterRequestHandler(Commands.BaseReown); - RegisterRequestHandler(Commands.BaseSetDockingRights); - RegisterRequestHandler(Commands.BaseSelect); - RegisterRequestHandler(Commands.BaseGetInfo); - RegisterRequestHandler(Commands.BaseGetMyItems); - RegisterRequestHandler(Commands.BaseListFacilities).SingleInstance(); - - RegisterRequestHandler(Commands.GetZoneInfo); - RegisterRequestHandler(Commands.ItemCountOnZone); - - - RegisterRequestHandler(Commands.CorporationCreate); - RegisterRequestHandler(Commands.CorporationRemoveMember); - RegisterRequestHandler(Commands.CorporationGetMyInfo); - RegisterRequestHandler(Commands.CorporationSetMemberRole); - RegisterRequestHandler(Commands.CorporationCharacterInvite); - RegisterRequestHandler(Commands.CorporationInviteReply); - RegisterRequestHandler(Commands.CorporationInfo); - RegisterRequestHandler(Commands.CorporationLeave); - RegisterRequestHandler(Commands.CorporationSearch); - RegisterRequestHandler(Commands.CorporationSetInfo); - RegisterRequestHandler(Commands.CorporationDropRoles); - RegisterRequestHandler(Commands.CorporationCancelLeave); - RegisterRequestHandler(Commands.CorporationPayOut); - RegisterRequestHandler(Commands.CorporationForceInfo); - RegisterRequestHandler(Commands.CorporationGetDelegates); - RegisterRequestHandler(Commands.CorporationTransfer); - RegisterRequestHandler(Commands.CorporationHangarListAll); - RegisterRequestHandler(Commands.CorporationHangarListOnBase); - RegisterRequestHandler(Commands.CorporationRentHangar); - RegisterRequestHandler(Commands.CorporationHangarPayRent); - RegisterRequestHandler(Commands.CorporationHangarLogSet); - RegisterRequestHandler(Commands.CorporationHangarLogClear); - RegisterRequestHandler(Commands.CorporationHangarLogList); - RegisterRequestHandler(Commands.CorporationHangarSetAccess); - RegisterRequestHandler(Commands.CorporationHangarClose); - RegisterRequestHandler(Commands.CorporationHangarSetName); - RegisterRequestHandler(Commands.CorporationHangarRentPrice); - RegisterRequestHandler(Commands.CorporationHangarFolderSectionCreate); - RegisterRequestHandler(Commands.CorporationHangarFolderSectionDelete); - RegisterRequestHandler(Commands.CorporationVoteStart); - RegisterRequestHandler(Commands.CorporationVoteList); - RegisterRequestHandler(Commands.CorporationVoteDelete); - RegisterRequestHandler(Commands.CorporationVoteCast); - RegisterRequestHandler(Commands.CorporationVoteSetTopic); - RegisterRequestHandler(Commands.CorporationBulletinStart); - RegisterRequestHandler(Commands.CorporationBulletinEntry); - RegisterRequestHandler(Commands.CorporationBulletinDelete); - RegisterRequestHandler(Commands.CorporationBulletinList); - RegisterRequestHandler(Commands.CorporationBulletinDetails); - RegisterRequestHandler(Commands.CorporationBulletinEntryDelete); - RegisterRequestHandler(Commands.CorporationBulletinNewEntries); - RegisterRequestHandler(Commands.CorporationBulletinModerate); - RegisterRequestHandler(Commands.CorporationCeoTakeOverStatus); - RegisterRequestHandler(Commands.CorporationVolunteerForCeo); - RegisterRequestHandler(Commands.CorporationRename); - RegisterRequestHandler(Commands.CorporationDonate); - RegisterRequestHandler(Commands.CorporationTransactionHistory); - RegisterRequestHandler(Commands.CorporationApply); - RegisterRequestHandler(Commands.CorporationDeleteMyApplication); - RegisterRequestHandler(Commands.CorporationAcceptApplication); - RegisterRequestHandler(Commands.CorporationDeleteApplication); - RegisterRequestHandler(Commands.CorporationListMyApplications); - RegisterRequestHandler(Commands.CorporationListApplications); - RegisterRequestHandler(Commands.CorporationLogHistory); - RegisterRequestHandler(Commands.CorporationNameHistory); - RegisterRequestHandler(Commands.CorporationSetColor); - RegisterRequestHandler(Commands.CorporationDocumentConfig).SingleInstance(); - RegisterRequestHandler(Commands.CorporationDocumentTransfer); - RegisterRequestHandler(Commands.CorporationDocumentList); - RegisterRequestHandler(Commands.CorporationDocumentCreate); - RegisterRequestHandler(Commands.CorporationDocumentDelete); - RegisterRequestHandler(Commands.CorporationDocumentOpen); - RegisterRequestHandler(Commands.CorporationDocumentUpdateBody); - RegisterRequestHandler(Commands.CorporationDocumentMonitor); - RegisterRequestHandler(Commands.CorporationDocumentUnmonitor); - RegisterRequestHandler(Commands.CorporationDocumentRent); - RegisterRequestHandler(Commands.CorporationDocumentRegisterList); - RegisterRequestHandler(Commands.CorporationDocumentRegisterSet); - RegisterRequestHandler(Commands.CorporationInfoFlushCache); - RegisterRequestHandler(Commands.CorporationGetReputation); - RegisterRequestHandler(Commands.CorporationMyStandings); - RegisterRequestHandler(Commands.CorporationSetMembersNeutral); - RegisterRequestHandler(Commands.CorporationRoleHistory); - RegisterRequestHandler(Commands.CorporationMemberRoleHistory); - - - - - RegisterRequestHandler(Commands.YellowPagesSearch); - RegisterRequestHandler(Commands.YellowPagesSubmit); - RegisterRequestHandler(Commands.YellowPagesGet); - RegisterRequestHandler(Commands.YellowPagesDelete); - - - RegisterRequestHandler(Commands.AllianceGetDefaults).SingleInstance(); - RegisterRequestHandler(Commands.AllianceGetMyInfo); - RegisterRequestHandler(Commands.AllianceRoleHistory); - - RegisterRequestHandler(Commands.ExtensionTest); - RegisterRequestHandler(Commands.ExtensionGetAll).SingleInstance(); - RegisterRequestHandler(Commands.ExtensionPrerequireList).SingleInstance(); - RegisterRequestHandler(Commands.ExtensionCategoryList).SingleInstance(); - RegisterRequestHandler(Commands.ExtensionLearntList); - RegisterRequestHandler(Commands.ExtensionGetAvailablePoints); - RegisterRequestHandler(Commands.ExtensionGetPointParameters); - RegisterRequestHandler(Commands.ExtensionHistory); - RegisterRequestHandler(Commands.ExtensionBuyForPoints); - RegisterRequestHandler(Commands.ExtensionRemoveLevel); - RegisterRequestHandler(Commands.ExtensionBuyEpBoost); - RegisterRequestHandler(Commands.ExtensionResetCharacter); - RegisterRequestHandler(Commands.ExtensionFreeLockedEp); - RegisterRequestHandler(Commands.ExtensionGive); - RegisterRequestHandler(Commands.ExtensionReset); - RegisterRequestHandler(Commands.ExtensionRevert); - - RegisterRequestHandler(Commands.ItemShopBuy); - RegisterRequestHandler(Commands.ItemShopList); - RegisterRequestHandler(Commands.GiftOpen); - RegisterRequestHandler(Commands.GetHighScores); - RegisterRequestHandler(Commands.GetMyHighScores); - RegisterRequestHandler(Commands.ZoneSectorList).SingleInstance(); - - RegisterRequestHandler(Commands.ListContainer); - - RegisterRequestHandler(Commands.SocialGetMyList); - RegisterRequestHandler(Commands.SocialFriendRequest); - RegisterRequestHandler(Commands.SocialConfirmPendingFriendRequest); - RegisterRequestHandler(Commands.SocialDeleteFriend); - RegisterRequestHandler(Commands.SocialBlockFriend); - - RegisterRequestHandler(Commands.PBSGetReimburseInfo); - RegisterRequestHandler(Commands.PBSSetReimburseInfo); - RegisterRequestHandler(Commands.PBSGetLog); - - RegisterRequestHandler(Commands.MineralScanResultList); - RegisterRequestHandler(Commands.MineralScanResultMove); - RegisterRequestHandler(Commands.MineralScanResultDelete); - RegisterRequestHandler(Commands.MineralScanResultCreateItem); - RegisterRequestHandler(Commands.MineralScanResultUploadFromItem); - - RegisterRequestHandler(Commands.FreshNewsCount); - RegisterRequestHandler(Commands.GetNews); - RegisterRequestHandler(Commands.AddNews); - RegisterRequestHandler(Commands.NewsCategory).SingleInstance(); - - RegisterRequestHandler(Commands.EpForActivityDailyLog); - RegisterRequestHandler(Commands.GetMyKillReports); - RegisterRequestHandler(Commands.UseLotteryItem); - RegisterRequestHandler(Commands.ContainerMover); - - - RegisterRequestHandler(Commands.MarketTaxChange); - RegisterRequestHandler(Commands.MarketTaxLogList); - RegisterRequestHandler(Commands.MarketGetInfo); - RegisterRequestHandler(Commands.MarketAddCategory); - RegisterRequestHandler(Commands.MarketItemList); - RegisterRequestHandler(Commands.MarketGetMyItems); - RegisterRequestHandler(Commands.MarketGetAveragePrices); - RegisterRequestHandler(Commands.MarketCreateBuyOrder); - RegisterRequestHandler(Commands.MarketCreateSellOrder); - RegisterRequestHandler(Commands.MarketBuyItem); - RegisterRequestHandler(Commands.MarketCancelItem); - RegisterRequestHandler(Commands.MarketGetState); - RegisterRequestHandler(Commands.MarketSetState); - RegisterRequestHandler(Commands.MarketFlush); - RegisterRequestHandler(Commands.MarketGetDefinitionAveragePrice); - RegisterRequestHandler(Commands.MarketAvailableItems); - RegisterRequestHandler(Commands.MarketItemsInRange); - RegisterRequestHandler(Commands.MarketInsertStats); - RegisterRequestHandler(Commands.MarketListFacilities); - RegisterRequestHandler(Commands.MarketInsertAverageForCF); - RegisterRequestHandler(Commands.MarketGlobalAveragePrices); - RegisterRequestHandler(Commands.MarketModifyOrder); - RegisterRequestHandler(Commands.MarketCreateGammaPlasmaOrders); - RegisterRequestHandler(Commands.MarketRemoveItems); - RegisterRequestHandler(Commands.MarketCleanUp); - - - - RegisterRequestHandler(Commands.TradeBegin); - RegisterRequestHandler(Commands.TradeCancel); - RegisterRequestHandler(Commands.TradeSetOffer); - RegisterRequestHandler(Commands.TradeAccept); - RegisterRequestHandler(Commands.TradeRetractOffer); - - - RegisterRequestHandler(Commands.GetRobotInfo).OnActivated(e => e.Instance.ForFitting = false); - RegisterRequestHandler(Commands.GetRobotFittingInfo); - RegisterRequestHandler(Commands.SelectActiveRobot); - RegisterRequestHandler(Commands.RequestStarterRobot); - RegisterRequestHandler(Commands.RobotEmpty); - RegisterRequestHandler(Commands.SetRobotTint); - - RegisterRequestHandler(Commands.FittingPresetList); - RegisterRequestHandler(Commands.FittingPresetSave); - RegisterRequestHandler(Commands.FittingPresetDelete); - RegisterRequestHandler(Commands.FittingPresetApply); - - RegisterRequestHandler(Commands.RobotTemplateAdd); - RegisterRequestHandler(Commands.RobotTemplateUpdate); - RegisterRequestHandler(Commands.RobotTemplateDelete); - RegisterRequestHandler(Commands.RobotTemplateList); - RegisterRequestHandler(Commands.RobotTemplateBuild); - - RegisterRequestHandler(Commands.EquipModule); - RegisterRequestHandler(Commands.ChangeModule); - RegisterRequestHandler(Commands.RemoveModule); - RegisterRequestHandler(Commands.EquipAmmo); - RegisterRequestHandler(Commands.ChangeAmmo); - RegisterRequestHandler(Commands.UnequipAmmo); - RegisterRequestHandler(Commands.PackItems); - RegisterRequestHandler(Commands.UnpackItems); - RegisterRequestHandler(Commands.TrashItems); - RegisterRequestHandler(Commands.RelocateItems); - RegisterRequestHandler(Commands.StackSelection); - RegisterRequestHandler(Commands.UnstackAmount); - RegisterRequestHandler(Commands.SetItemName); - RegisterRequestHandler(Commands.StackTo); - RegisterRequestHandler(Commands.ServerMessage); - RegisterRequestHandler(Commands.RequestInfiniteBox); - RegisterRequestHandler(Commands.DecorCategoryList); - RegisterRequestHandler(Commands.PollGet); - RegisterRequestHandler(Commands.PollAnswer); - RegisterRequestHandler(Commands.ForceDock); - RegisterRequestHandler(Commands.GetItemSummary); - - RegisterRequestHandler(Commands.ProductionHistory); - RegisterRequestHandler(Commands.GetResearchLevels).SingleInstance(); - RegisterRequestHandler(Commands.ProductionComponentsList); - RegisterRequestHandler(Commands.ProductionRefine); - RegisterRequestHandler(Commands.ProductionRefineQuery); - RegisterRequestHandler(Commands.ProductionCPRGInfo); - RegisterRequestHandler(Commands.ProductionCPRGForge); - RegisterRequestHandler(Commands.ProductionCPRGForgeQuery); - RegisterRequestHandler(Commands.ProductionGetCprgFromLine); - RegisterRequestHandler(Commands.ProductionGetCprgFromLineQuery); - RegisterRequestHandler(Commands.ProductionLineSetRounds); - RegisterRequestHandler(Commands.ProductionPrototypeStart); - RegisterRequestHandler(Commands.ProductionPrototypeQuery); - RegisterRequestHandler(Commands.ProductionInsuranceQuery); - RegisterRequestHandler(Commands.ProductionInsuranceList); - RegisterRequestHandler(Commands.ProductionInsuranceBuy); - RegisterRequestHandler(Commands.ProductionInsuranceDelete); - RegisterRequestHandler(Commands.ProductionMergeResearchKitsMulti); - RegisterRequestHandler(Commands.ProductionMergeResearchKitsMultiQuery); - RegisterRequestHandler(Commands.ProductionQueryLineNextRound); - RegisterRequestHandler(Commands.ProductionReprocess); - RegisterRequestHandler(Commands.ProductionReprocessQuery); - RegisterRequestHandler(Commands.ProductionRepair); - RegisterRequestHandler(Commands.ProductionRepairQuery); - RegisterRequestHandler(Commands.ProductionResearch); - RegisterRequestHandler(Commands.ProductionResearchQuery); - RegisterRequestHandler(Commands.ProductionInProgress); - RegisterRequestHandler(Commands.ProductionCancel); - RegisterRequestHandler(Commands.ProductionFacilityInfo); - RegisterRequestHandler(Commands.ProductionLineList); - RegisterRequestHandler(Commands.ProductionLineCalibrate); - RegisterRequestHandler(Commands.ProductionLineDelete); - RegisterRequestHandler(Commands.ProductionLineStart); - RegisterRequestHandler(Commands.ProductionFacilityDescription); - RegisterRequestHandler(Commands.ProductionInProgressCorporation); + _ = RegisterRequestHandler(Commands.GetEnums); + _ = RegisterRequestHandler(Commands.GetCommands); + _ = RegisterRequestHandler(Commands.GetEntityDefaults).SingleInstance(); + _ = RegisterRequestHandler(Commands.GetAggregateFields).SingleInstance(); + _ = RegisterRequestHandler(Commands.GetDefinitionConfigUnits).SingleInstance(); + _ = RegisterRequestHandler(Commands.GetEffects).SingleInstance(); + _ = RegisterRequestHandler(Commands.GetDistances); + _ = RegisterRequestHandler(Commands.SignIn); + _ = RegisterRequestHandler(Commands.SignInSteam); + _ = RegisterRequestHandler(Commands.SignOut); + _ = RegisterRequestHandler(Commands.SteamListAccounts); + _ = RegisterRequestHandler(Commands.AccountConfirmEmail); + _ = RegisterRequestHandler(Commands.CharacterList); + _ = RegisterRequestHandler(Commands.CharacterCreate); + _ = RegisterRequestHandler(Commands.CharacterSelect); + _ = RegisterRequestHandler(Commands.CharacterDeselect); + _ = RegisterRequestHandler(Commands.CharacterForceDeselect); + _ = RegisterRequestHandler(Commands.CharacterForceDisconnect); + _ = RegisterRequestHandler(Commands.CharacterDelete); + _ = RegisterRequestHandler(Commands.CharacterSetHomeBase); + _ = RegisterRequestHandler(Commands.CharacterGetProfiles); + _ = RegisterRequestHandler(Commands.CharacterRename); + _ = RegisterRequestHandler(Commands.CharacterCheckNick); + _ = RegisterRequestHandler(Commands.CharacterCorrectNick); + _ = RegisterRequestHandler(Commands.IsOnline); + _ = RegisterRequestHandler(Commands.CharacterSettingsSet); + _ = RegisterRequestHandler(Commands.CharacterSetMoodMessage); + _ = RegisterRequestHandler(Commands.CharacterTransferCredit); + _ = RegisterRequestHandler(Commands.CharacterSetAvatar); + _ = RegisterRequestHandler(Commands.CharacterSetBlockTrades); + _ = RegisterRequestHandler(Commands.CharacterSetCredit); + _ = RegisterRequestHandler(Commands.CharacterClearHomeBase); + _ = RegisterRequestHandler(Commands.CharacterSettingsGet); + _ = RegisterRequestHandler(Commands.CharacterGetMyProfile); + _ = RegisterRequestHandler(Commands.CharacterSearch); + _ = RegisterRequestHandler(Commands.CharacterRemoveFromCache); + _ = RegisterRequestHandler(Commands.CharacterListNpcDeath); + _ = RegisterRequestHandler(Commands.CharacterTransactionHistory); + _ = RegisterRequestHandler(Commands.CharacterGetZoneInfo); + _ = RegisterRequestHandler(Commands.CharacterNickHistory); + _ = RegisterRequestHandler(Commands.CharacterGetNote); + _ = RegisterRequestHandler(Commands.CharacterSetNote); + _ = RegisterRequestHandler(Commands.CharacterCorporationHistory); + _ = RegisterRequestHandler(Commands.CharacterWizardData).SingleInstance(); + _ = RegisterRequestHandler(Commands.GetCharactersOnline); + _ = RegisterRequestHandler(Commands.ReimburseItem); + _ = RegisterRequestHandler(Commands.Chat); + _ = RegisterRequestHandler(Commands.GoodiePackList); + _ = RegisterRequestHandler(Commands.GoodiePackRedeem); + _ = RegisterRequestHandler(Commands.Ping); + _ = RegisterRequestHandler(Commands.Quit); + _ = RegisterRequestHandler(Commands.SetMaxUserCount); + _ = RegisterRequestHandler(Commands.SparkTeleportSet); + _ = RegisterRequestHandler(Commands.SparkTeleportUse); + _ = RegisterRequestHandler(Commands.SparkTeleportDelete); + _ = RegisterRequestHandler(Commands.SparkTeleportList); + _ = RegisterRequestHandler(Commands.SparkChange); + _ = RegisterRequestHandler(Commands.SparkRemove); + _ = RegisterRequestHandler(Commands.SparkList); + _ = RegisterRequestHandler(Commands.SparkSetDefault); + _ = RegisterRequestHandler(Commands.SparkUnlock); + _ = RegisterRequestHandler(Commands.Undock) + ; + _ = RegisterRequestHandler(Commands.ProximityProbeRegisterSet); + _ = RegisterRequestHandler(Commands.ProximityProbeSetName); + _ = RegisterRequestHandler(Commands.ProximityProbeList); + _ = RegisterRequestHandler(Commands.ProximityProbeGetRegistrationInfo); + + _ = RegisterRequestHandler(Commands.IntrusionEnabler); + _ = RegisterRequestHandler(Commands.AccountGetTransactionHistory); + _ = RegisterRequestHandler(Commands.AccountList); + + _ = RegisterRequestHandler(Commands.AccountEpForActivityHistory); + _ = RegisterRequestHandler(Commands.RedeemableItemList); + _ = RegisterRequestHandler(Commands.RedeemableItemRedeem); + _ = RegisterRequestHandler(Commands.RedeemableItemActivate); + _ = RegisterRequestHandler(Commands.CreateItem); + _ = RegisterRequestHandler(Commands.TeleportList); + _ = RegisterRequestHandler(Commands.TeleportConnectColumns); + _ = RegisterRequestHandler(Commands.EnableSelfTeleport); + _ = RegisterRequestHandler(Commands.ItemCount); + _ = RegisterRequestHandler(Commands.SystemInfo); + _ = RegisterRequestHandler(Commands.TransferData); + + _ = RegisterRequestHandler(Commands.BaseReown); + _ = RegisterRequestHandler(Commands.BaseSetDockingRights); + _ = RegisterRequestHandler(Commands.BaseSelect); + _ = RegisterRequestHandler(Commands.BaseGetInfo); + _ = RegisterRequestHandler(Commands.BaseGetMyItems); + _ = RegisterRequestHandler(Commands.BaseListFacilities).SingleInstance(); + + _ = RegisterRequestHandler(Commands.GetZoneInfo); + _ = RegisterRequestHandler(Commands.ItemCountOnZone); + + + _ = RegisterRequestHandler(Commands.CorporationCreate); + _ = RegisterRequestHandler(Commands.CorporationRemoveMember); + _ = RegisterRequestHandler(Commands.CorporationGetMyInfo); + _ = RegisterRequestHandler(Commands.CorporationSetMemberRole); + _ = RegisterRequestHandler(Commands.CorporationCharacterInvite); + _ = RegisterRequestHandler(Commands.CorporationInviteReply); + _ = RegisterRequestHandler(Commands.CorporationInfo); + _ = RegisterRequestHandler(Commands.CorporationLeave); + _ = RegisterRequestHandler(Commands.CorporationSearch); + _ = RegisterRequestHandler(Commands.CorporationSetInfo); + _ = RegisterRequestHandler(Commands.CorporationDropRoles); + _ = RegisterRequestHandler(Commands.CorporationCancelLeave); + _ = RegisterRequestHandler(Commands.CorporationPayOut); + _ = RegisterRequestHandler(Commands.CorporationForceInfo); + _ = RegisterRequestHandler(Commands.CorporationGetDelegates); + _ = RegisterRequestHandler(Commands.CorporationTransfer); + _ = RegisterRequestHandler(Commands.CorporationHangarListAll); + _ = RegisterRequestHandler(Commands.CorporationHangarListOnBase); + _ = RegisterRequestHandler(Commands.CorporationRentHangar); + _ = RegisterRequestHandler(Commands.CorporationHangarPayRent); + _ = RegisterRequestHandler(Commands.CorporationHangarLogSet); + _ = RegisterRequestHandler(Commands.CorporationHangarLogClear); + _ = RegisterRequestHandler(Commands.CorporationHangarLogList); + _ = RegisterRequestHandler(Commands.CorporationHangarSetAccess); + _ = RegisterRequestHandler(Commands.CorporationHangarClose); + _ = RegisterRequestHandler(Commands.CorporationHangarSetName); + _ = RegisterRequestHandler(Commands.CorporationHangarRentPrice); + _ = RegisterRequestHandler(Commands.CorporationHangarFolderSectionCreate); + _ = RegisterRequestHandler(Commands.CorporationHangarFolderSectionDelete); + _ = RegisterRequestHandler(Commands.CorporationVoteStart); + _ = RegisterRequestHandler(Commands.CorporationVoteList); + _ = RegisterRequestHandler(Commands.CorporationVoteDelete); + _ = RegisterRequestHandler(Commands.CorporationVoteCast); + _ = RegisterRequestHandler(Commands.CorporationVoteSetTopic); + _ = RegisterRequestHandler(Commands.CorporationBulletinStart); + _ = RegisterRequestHandler(Commands.CorporationBulletinEntry); + _ = RegisterRequestHandler(Commands.CorporationBulletinDelete); + _ = RegisterRequestHandler(Commands.CorporationBulletinList); + _ = RegisterRequestHandler(Commands.CorporationBulletinDetails); + _ = RegisterRequestHandler(Commands.CorporationBulletinEntryDelete); + _ = RegisterRequestHandler(Commands.CorporationBulletinNewEntries); + _ = RegisterRequestHandler(Commands.CorporationBulletinModerate); + _ = RegisterRequestHandler(Commands.CorporationCeoTakeOverStatus); + _ = RegisterRequestHandler(Commands.CorporationVolunteerForCeo); + _ = RegisterRequestHandler(Commands.CorporationRename); + _ = RegisterRequestHandler(Commands.CorporationDonate); + _ = RegisterRequestHandler(Commands.CorporationTransactionHistory); + _ = RegisterRequestHandler(Commands.CorporationApply); + _ = RegisterRequestHandler(Commands.CorporationDeleteMyApplication); + _ = RegisterRequestHandler(Commands.CorporationAcceptApplication); + _ = RegisterRequestHandler(Commands.CorporationDeleteApplication); + _ = RegisterRequestHandler(Commands.CorporationListMyApplications); + _ = RegisterRequestHandler(Commands.CorporationListApplications); + _ = RegisterRequestHandler(Commands.CorporationLogHistory); + _ = RegisterRequestHandler(Commands.CorporationNameHistory); + _ = RegisterRequestHandler(Commands.CorporationSetColor); + _ = RegisterRequestHandler(Commands.CorporationDocumentConfig).SingleInstance(); + _ = RegisterRequestHandler(Commands.CorporationDocumentTransfer); + _ = RegisterRequestHandler(Commands.CorporationDocumentList); + _ = RegisterRequestHandler(Commands.CorporationDocumentCreate); + _ = RegisterRequestHandler(Commands.CorporationDocumentDelete); + _ = RegisterRequestHandler(Commands.CorporationDocumentOpen); + _ = RegisterRequestHandler(Commands.CorporationDocumentUpdateBody); + _ = RegisterRequestHandler(Commands.CorporationDocumentMonitor); + _ = RegisterRequestHandler(Commands.CorporationDocumentUnmonitor); + _ = RegisterRequestHandler(Commands.CorporationDocumentRent); + _ = RegisterRequestHandler(Commands.CorporationDocumentRegisterList); + _ = RegisterRequestHandler(Commands.CorporationDocumentRegisterSet); + _ = RegisterRequestHandler(Commands.CorporationInfoFlushCache); + _ = RegisterRequestHandler(Commands.CorporationGetReputation); + _ = RegisterRequestHandler(Commands.CorporationMyStandings); + _ = RegisterRequestHandler(Commands.CorporationSetMembersNeutral); + _ = RegisterRequestHandler(Commands.CorporationRoleHistory); + _ = RegisterRequestHandler(Commands.CorporationMemberRoleHistory); + + + + + _ = RegisterRequestHandler(Commands.YellowPagesSearch); + _ = RegisterRequestHandler(Commands.YellowPagesSubmit); + _ = RegisterRequestHandler(Commands.YellowPagesGet); + _ = RegisterRequestHandler(Commands.YellowPagesDelete); + + + _ = RegisterRequestHandler(Commands.AllianceGetDefaults).SingleInstance(); + _ = RegisterRequestHandler(Commands.AllianceGetMyInfo); + _ = RegisterRequestHandler(Commands.AllianceRoleHistory); + + _ = RegisterRequestHandler(Commands.ExtensionTest); + _ = RegisterRequestHandler(Commands.ExtensionGetAll).SingleInstance(); + _ = RegisterRequestHandler(Commands.ExtensionPrerequireList).SingleInstance(); + _ = RegisterRequestHandler(Commands.ExtensionCategoryList).SingleInstance(); + _ = RegisterRequestHandler(Commands.ExtensionLearntList); + _ = RegisterRequestHandler(Commands.ExtensionGetAvailablePoints); + _ = RegisterRequestHandler(Commands.ExtensionGetPointParameters); + _ = RegisterRequestHandler(Commands.ExtensionHistory); + _ = RegisterRequestHandler(Commands.ExtensionBuyForPoints); + _ = RegisterRequestHandler(Commands.ExtensionRemoveLevel); + _ = RegisterRequestHandler(Commands.ExtensionBuyEpBoost); + _ = RegisterRequestHandler(Commands.ExtensionResetCharacter); + _ = RegisterRequestHandler(Commands.ExtensionFreeLockedEp); + _ = RegisterRequestHandler(Commands.ExtensionFreeAllLockedEpCommand); // For GameAdmin Channel Command + _ = RegisterRequestHandler(Commands.ExtensionGive); + _ = RegisterRequestHandler(Commands.ExtensionReset); + _ = RegisterRequestHandler(Commands.ExtensionRevert); + + _ = RegisterRequestHandler(Commands.ItemShopBuy); + _ = RegisterRequestHandler(Commands.ItemShopList); + _ = RegisterRequestHandler(Commands.GiftOpen); + _ = RegisterRequestHandler(Commands.GetHighScores); + _ = RegisterRequestHandler(Commands.GetMyHighScores); + _ = RegisterRequestHandler(Commands.ZoneSectorList).SingleInstance(); + + _ = RegisterRequestHandler(Commands.ListContainer); + + _ = RegisterRequestHandler(Commands.SocialGetMyList); + _ = RegisterRequestHandler(Commands.SocialFriendRequest); + _ = RegisterRequestHandler(Commands.SocialConfirmPendingFriendRequest); + _ = RegisterRequestHandler(Commands.SocialDeleteFriend); + _ = RegisterRequestHandler(Commands.SocialBlockFriend); + + _ = RegisterRequestHandler(Commands.PBSGetReimburseInfo); + _ = RegisterRequestHandler(Commands.PBSSetReimburseInfo); + _ = RegisterRequestHandler(Commands.PBSGetLog); + + _ = RegisterRequestHandler(Commands.MineralScanResultList); + _ = RegisterRequestHandler(Commands.MineralScanResultMove); + _ = RegisterRequestHandler(Commands.MineralScanResultDelete); + _ = RegisterRequestHandler(Commands.MineralScanResultCreateItem); + _ = RegisterRequestHandler(Commands.MineralScanResultUploadFromItem); + + _ = RegisterRequestHandler(Commands.FreshNewsCount); + _ = RegisterRequestHandler(Commands.GetNews); + _ = RegisterRequestHandler(Commands.AddNews); + _ = RegisterRequestHandler(Commands.UpdateNews); + _ = RegisterRequestHandler(Commands.NewsCategory).SingleInstance(); + + _ = RegisterRequestHandler(Commands.EpForActivityDailyLog); + _ = RegisterRequestHandler(Commands.GetMyKillReports); + _ = RegisterRequestHandler(Commands.UseLotteryItem); + _ = RegisterRequestHandler(Commands.ContainerMover); + + + _ = RegisterRequestHandler(Commands.MarketTaxChange); + _ = RegisterRequestHandler(Commands.MarketTaxLogList); + _ = RegisterRequestHandler(Commands.MarketGetInfo); + _ = RegisterRequestHandler(Commands.MarketAddCategory); + _ = RegisterRequestHandler(Commands.MarketItemList); + _ = RegisterRequestHandler(Commands.MarketGetMyItems); + _ = RegisterRequestHandler(Commands.MarketGetAveragePrices); + _ = RegisterRequestHandler(Commands.MarketCreateBuyOrder); + _ = RegisterRequestHandler(Commands.MarketCreateSellOrder); + _ = RegisterRequestHandler(Commands.MarketBuyItem); + _ = RegisterRequestHandler(Commands.MarketCancelItem); + _ = RegisterRequestHandler(Commands.MarketGetState); + _ = RegisterRequestHandler(Commands.MarketSetState); + _ = RegisterRequestHandler(Commands.MarketFlush); + _ = RegisterRequestHandler(Commands.MarketGetDefinitionAveragePrice); + _ = RegisterRequestHandler(Commands.MarketAvailableItems); + _ = RegisterRequestHandler(Commands.MarketItemsInRange); + _ = RegisterRequestHandler(Commands.MarketInsertStats); + _ = RegisterRequestHandler(Commands.MarketListFacilities); + _ = RegisterRequestHandler(Commands.MarketInsertAverageForCF); + _ = RegisterRequestHandler(Commands.MarketGlobalAveragePrices); + _ = RegisterRequestHandler(Commands.MarketModifyOrder); + _ = RegisterRequestHandler(Commands.MarketCreateGammaPlasmaOrders); + _ = RegisterRequestHandler(Commands.MarketRemoveItems); + _ = RegisterRequestHandler(Commands.MarketCleanUp); + + + + _ = RegisterRequestHandler(Commands.TradeBegin); + _ = RegisterRequestHandler(Commands.TradeCancel); + _ = RegisterRequestHandler(Commands.TradeSetOffer); + _ = RegisterRequestHandler(Commands.TradeAccept); + _ = RegisterRequestHandler(Commands.TradeRetractOffer); + + + _ = RegisterRequestHandler(Commands.GetRobotInfo).OnActivated(e => e.Instance.ForFitting = false); + _ = RegisterRequestHandler(Commands.GetRobotFittingInfo); + _ = RegisterRequestHandler(Commands.SelectActiveRobot); + _ = RegisterRequestHandler(Commands.RequestStarterRobot); + _ = RegisterRequestHandler(Commands.RobotEmpty); + _ = RegisterRequestHandler(Commands.SetRobotTint); + + _ = RegisterRequestHandler(Commands.FittingPresetList); + _ = RegisterRequestHandler(Commands.FittingPresetSave); + _ = RegisterRequestHandler(Commands.FittingPresetDelete); + _ = RegisterRequestHandler(Commands.FittingPresetApply); + + _ = RegisterRequestHandler(Commands.RobotTemplateAdd); + _ = RegisterRequestHandler(Commands.RobotTemplateUpdate); + _ = RegisterRequestHandler(Commands.RobotTemplateDelete); + _ = RegisterRequestHandler(Commands.RobotTemplateList); + _ = RegisterRequestHandler(Commands.RobotTemplateBuild); + + _ = RegisterRequestHandler(Commands.EquipModule); + _ = RegisterRequestHandler(Commands.ChangeModule); + _ = RegisterRequestHandler(Commands.RemoveModule); + _ = RegisterRequestHandler(Commands.EquipAmmo); + _ = RegisterRequestHandler(Commands.ChangeAmmo); + _ = RegisterRequestHandler(Commands.UnequipAmmo); + _ = RegisterRequestHandler(Commands.PackItems); + _ = RegisterRequestHandler(Commands.UnpackItems); + _ = RegisterRequestHandler(Commands.TrashItems); + _ = RegisterRequestHandler(Commands.RelocateItems); + _ = RegisterRequestHandler(Commands.StackSelection); + _ = RegisterRequestHandler(Commands.UnstackAmount); + _ = RegisterRequestHandler(Commands.SetItemName); + _ = RegisterRequestHandler(Commands.StackTo); + _ = RegisterRequestHandler(Commands.ServerMessage); + _ = RegisterRequestHandler(Commands.RequestInfiniteBox); + _ = RegisterRequestHandler(Commands.DecorCategoryList); + _ = RegisterRequestHandler(Commands.PollGet); + _ = RegisterRequestHandler(Commands.PollAnswer); + _ = RegisterRequestHandler(Commands.ForceDock); + _ = RegisterRequestHandler(Commands.ForceDockAdmin); + _ = RegisterRequestHandler(Commands.GetItemSummary); + + _ = RegisterRequestHandler(Commands.ProductionHistory); + _ = RegisterRequestHandler(Commands.GetResearchLevels).SingleInstance(); + _ = RegisterRequestHandler(Commands.ProductionComponentsList); + _ = RegisterRequestHandler(Commands.ProductionRefine); + _ = RegisterRequestHandler(Commands.ProductionRefineQuery); + _ = RegisterRequestHandler(Commands.ProductionCPRGInfo); + _ = RegisterRequestHandler(Commands.ProductionCPRGForge); + _ = RegisterRequestHandler(Commands.ProductionCPRGForgeQuery); + _ = RegisterRequestHandler(Commands.ProductionGetCprgFromLine); + _ = RegisterRequestHandler(Commands.ProductionGetCprgFromLineQuery); + _ = RegisterRequestHandler(Commands.ProductionLineSetRounds); + _ = RegisterRequestHandler(Commands.ProductionPrototypeStart); + _ = RegisterRequestHandler(Commands.ProductionPrototypeQuery); + _ = RegisterRequestHandler(Commands.ProductionInsuranceQuery); + _ = RegisterRequestHandler(Commands.ProductionInsuranceList); + _ = RegisterRequestHandler(Commands.ProductionInsuranceBuy); + _ = RegisterRequestHandler(Commands.ProductionInsuranceDelete); + _ = RegisterRequestHandler(Commands.ProductionMergeResearchKitsMulti); + _ = RegisterRequestHandler(Commands.ProductionMergeResearchKitsMultiQuery); + _ = RegisterRequestHandler(Commands.ProductionQueryLineNextRound); + _ = RegisterRequestHandler(Commands.ProductionReprocess); + _ = RegisterRequestHandler(Commands.ProductionReprocessQuery); + _ = RegisterRequestHandler(Commands.ProductionRepair); + _ = RegisterRequestHandler(Commands.ProductionRepairQuery); + _ = RegisterRequestHandler(Commands.ProductionResearch); + _ = RegisterRequestHandler(Commands.ProductionResearchQuery); + _ = RegisterRequestHandler(Commands.ProductionInProgress); + _ = RegisterRequestHandler(Commands.ProductionCancel); + _ = RegisterRequestHandler(Commands.ProductionFacilityInfo); + _ = RegisterRequestHandler(Commands.ProductionLineList); + _ = RegisterRequestHandler(Commands.ProductionLineCalibrate); + _ = RegisterRequestHandler(Commands.ProductionLineDelete); + _ = RegisterRequestHandler(Commands.ProductionLineStart); + _ = RegisterRequestHandler(Commands.ProductionFacilityDescription); + _ = RegisterRequestHandler(Commands.ProductionInProgressCorporation); //admin - RegisterRequestHandler(Commands.ProductionRemoveFacility); - RegisterRequestHandler(Commands.ProductionSpawnComponents); - RegisterRequestHandler(Commands.ProductionScaleComponentsAmount); - RegisterRequestHandler(Commands.ProductionUnrepairItem); - RegisterRequestHandler(Commands.ProductionFacilityOnOff); - RegisterRequestHandler(Commands.ProductionForceEnd); - RegisterRequestHandler(Commands.ProductionServerInfo); - RegisterRequestHandler(Commands.ProductionSpawnCPRG); - RegisterRequestHandler(Commands.ProductionGetInsurance); - RegisterRequestHandler(Commands.ProductionSetInsurance); - - RegisterRequestHandler(Commands.CreateCorporationHangarStorage); - RegisterRequestHandler(Commands.DockAll); - RegisterRequestHandler(Commands.ReturnCorporateOwnedItems); - - RegisterRequestHandler(Commands.RelayOpen); - RegisterRequestHandler(Commands.RelayClose); - RegisterRequestHandler(Commands.ZoneSaveLayer); - RegisterRequestHandler(Commands.ZoneRemoveObject); - RegisterRequestHandler(Commands.ZoneDebugLOS); - RegisterRequestHandler(Commands.ZoneSetBaseDetails); - RegisterRequestHandler(Commands.ZoneSelfDestruct); - RegisterRequestHandler(Commands.ZoneSOS); - - RegisterRequestHandler(Commands.ZoneGetZoneObjectDebugInfo); - RegisterRequestHandler(Commands.ZoneDrawBlockingByEid); - - - RegisterRequestHandler(Commands.GangCreate); - RegisterRequestHandler(Commands.GangDelete); - RegisterRequestHandler(Commands.GangLeave); - RegisterRequestHandler(Commands.GangKick); - RegisterRequestHandler(Commands.GangInfo); - RegisterRequestHandler(Commands.GangSetLeader); - RegisterRequestHandler(Commands.GangSetRole); - RegisterRequestHandler(Commands.GangInvite); - RegisterRequestHandler(Commands.GangInviteReply); - - RegisterRequestHandler(Commands.TechTreeInfo); - RegisterRequestHandler(Commands.TechTreeUnlock); - RegisterRequestHandler(Commands.TechTreeResearch); - RegisterRequestHandler(Commands.TechTreeDonate); - RegisterRequestHandler(Commands.TechTreeGetLogs); - - - RegisterRequestHandler(Commands.TransportAssignmentSubmit); - RegisterRequestHandler(Commands.TransportAssignmentList); - RegisterRequestHandler(Commands.TransportAssignmentCancel); - RegisterRequestHandler(Commands.TransportAssignmentTake); - RegisterRequestHandler(Commands.TransportAssignmentLog); - RegisterRequestHandler(Commands.TransportAssignmentContainerInfo); - RegisterRequestHandler(Commands.TransportAssignmentRunning); - RegisterRequestHandler(Commands.TransportAssignmentRetrieve); - RegisterRequestHandler(Commands.TransportAssignmentListContent); - RegisterRequestHandler(Commands.TransportAssignmentGiveUp); - RegisterRequestHandler(Commands.TransportAssignmentDeliver); - - - RegisterRequestHandler(Commands.SetStanding); - RegisterRequestHandler(Commands.ForceStanding); - RegisterRequestHandler(Commands.ForceFactionStandings); - RegisterRequestHandler(Commands.GetStandingForDefaultCorporations); - RegisterRequestHandler(Commands.GetStandingForDefaultAlliances); - RegisterRequestHandler(Commands.StandingList); - RegisterRequestHandler(Commands.StandingHistory); - RegisterRequestHandler(Commands.ReloadStandingForCharacter); - - RegisterRequestHandler(Commands.MailList); - RegisterRequestHandler(Commands.MailUsedFolders); - RegisterRequestHandler(Commands.MailSend); - RegisterRequestHandler(Commands.MailDelete); - RegisterRequestHandler(Commands.MailOpen); - RegisterRequestHandler(Commands.MailMoveToFolder); - RegisterRequestHandler(Commands.MailDeleteFolder); - RegisterRequestHandler(Commands.MailNewCount); - RegisterRequestHandler(Commands.MassMailOpen); - RegisterRequestHandler(Commands.MassMailDelete); - RegisterRequestHandler(Commands.MassMailSend); - RegisterRequestHandler(Commands.MassMailList); - RegisterRequestHandler(Commands.MassMailNewCount); - - - RegisterRequestHandler(Commands.ServerShutDownState); - RegisterRequestHandler(Commands.ServerShutDown); - RegisterRequestHandler(Commands.ServerShutDownCancel); + _ = RegisterRequestHandler(Commands.ProductionRemoveFacility); + _ = RegisterRequestHandler(Commands.ProductionSpawnComponents); + _ = RegisterRequestHandler(Commands.ProductionScaleComponentsAmount); + _ = RegisterRequestHandler(Commands.ProductionUnrepairItem); + _ = RegisterRequestHandler(Commands.ProductionFacilityOnOff); + _ = RegisterRequestHandler(Commands.ProductionForceEnd); + _ = RegisterRequestHandler(Commands.ProductionServerInfo); + _ = RegisterRequestHandler(Commands.ProductionSpawnCPRG); + _ = RegisterRequestHandler(Commands.ProductionGetInsurance); + _ = RegisterRequestHandler(Commands.ProductionSetInsurance); + + _ = RegisterRequestHandler(Commands.CreateCorporationHangarStorage); + _ = RegisterRequestHandler(Commands.DockAll); + _ = RegisterRequestHandler(Commands.ReturnCorporateOwnedItems); + + _ = RegisterRequestHandler(Commands.RelayOpen); + _ = RegisterRequestHandler(Commands.RelayClose); + _ = RegisterRequestHandler(Commands.ZoneSaveLayer); + _ = RegisterRequestHandler(Commands.ZoneRemoveObject); + _ = RegisterRequestHandler(Commands.ZoneDebugLOS); + _ = RegisterRequestHandler(Commands.ZoneSetBaseDetails); + _ = RegisterRequestHandler(Commands.ZoneSelfDestruct); + _ = RegisterRequestHandler(Commands.ZoneSOS); + _ = RegisterRequestHandler(Commands.ZoneCopyGroundType); //OPP + + _ = RegisterRequestHandler(Commands.ZoneGetZoneObjectDebugInfo); + _ = RegisterRequestHandler(Commands.ZoneDrawBlockingByEid); + + + _ = RegisterRequestHandler(Commands.GangCreate); + _ = RegisterRequestHandler(Commands.GangDelete); + _ = RegisterRequestHandler(Commands.GangLeave); + _ = RegisterRequestHandler(Commands.GangKick); + _ = RegisterRequestHandler(Commands.GangInfo); + _ = RegisterRequestHandler(Commands.GangSetLeader); + _ = RegisterRequestHandler(Commands.GangSetRole); + _ = RegisterRequestHandler(Commands.GangInvite); + _ = RegisterRequestHandler(Commands.GangInviteReply); + + _ = RegisterRequestHandler(Commands.TechTreeInfo); + _ = RegisterRequestHandler(Commands.TechTreeUnlock); + _ = RegisterRequestHandler(Commands.TechTreeResearch); + _ = RegisterRequestHandler(Commands.TechTreeDonate); + _ = RegisterRequestHandler(Commands.TechTreeGetLogs); + + + _ = RegisterRequestHandler(Commands.TransportAssignmentSubmit); + _ = RegisterRequestHandler(Commands.TransportAssignmentList); + _ = RegisterRequestHandler(Commands.TransportAssignmentCancel); + _ = RegisterRequestHandler(Commands.TransportAssignmentTake); + _ = RegisterRequestHandler(Commands.TransportAssignmentLog); + _ = RegisterRequestHandler(Commands.TransportAssignmentContainerInfo); + _ = RegisterRequestHandler(Commands.TransportAssignmentRunning); + _ = RegisterRequestHandler(Commands.TransportAssignmentRetrieve); + _ = RegisterRequestHandler(Commands.TransportAssignmentListContent); + _ = RegisterRequestHandler(Commands.TransportAssignmentGiveUp); + _ = RegisterRequestHandler(Commands.TransportAssignmentDeliver); + + + _ = RegisterRequestHandler(Commands.SetStanding); + _ = RegisterRequestHandler(Commands.ForceStanding); + _ = RegisterRequestHandler(Commands.ForceFactionStandings); + _ = RegisterRequestHandler(Commands.GetStandingForDefaultCorporations); + _ = RegisterRequestHandler(Commands.GetStandingForDefaultAlliances); + _ = RegisterRequestHandler(Commands.StandingList); + _ = RegisterRequestHandler(Commands.StandingHistory); + _ = RegisterRequestHandler(Commands.ReloadStandingForCharacter); + + _ = RegisterRequestHandler(Commands.MailList); + _ = RegisterRequestHandler(Commands.MailUsedFolders); + _ = RegisterRequestHandler(Commands.MailSend); + _ = RegisterRequestHandler(Commands.MailDelete); + _ = RegisterRequestHandler(Commands.MailOpen); + _ = RegisterRequestHandler(Commands.MailMoveToFolder); + _ = RegisterRequestHandler(Commands.MailDeleteFolder); + _ = RegisterRequestHandler(Commands.MailNewCount); + _ = RegisterRequestHandler(Commands.MassMailOpen); + _ = RegisterRequestHandler(Commands.MassMailDelete); + _ = RegisterRequestHandler(Commands.MassMailSend); + _ = RegisterRequestHandler(Commands.MassMailList); + _ = RegisterRequestHandler(Commands.MassMailNewCount); + + + _ = RegisterRequestHandler(Commands.ServerShutDownState); + _ = RegisterRequestHandler(Commands.ServerShutDown); + _ = RegisterRequestHandler(Commands.ServerShutDownCancel); RegisterZoneRequestHandlers(); //Admin tool commands - RegisterRequestHandler(Commands.GetAccountsWithCharacters); - RegisterRequestHandler(Commands.AccountGet); - RegisterRequestHandler(Commands.AccountUpdate); - RegisterRequestHandler(Commands.AccountCreate); - RegisterRequestHandler(Commands.ChangeSessionPassword); - RegisterRequestHandler(Commands.AccountBan); - RegisterRequestHandler(Commands.AccountUnban); - RegisterRequestHandler(Commands.AccountDelete); - RegisterRequestHandler(Commands.ServerInfoGet); - RegisterRequestHandler(Commands.ServerInfoSet); + _ = RegisterRequestHandler(Commands.GetAccountsWithCharacters); + _ = RegisterRequestHandler(Commands.AccountGet); + _ = RegisterRequestHandler(Commands.AccountUpdate); + _ = RegisterRequestHandler(Commands.AccountCreate); + _ = RegisterRequestHandler(Commands.ChangeSessionPassword); + _ = RegisterRequestHandler(Commands.AccountBan); + _ = RegisterRequestHandler(Commands.AccountUnban); + _ = RegisterRequestHandler(Commands.AccountDelete); + _ = RegisterRequestHandler(Commands.ServerInfoGet); + _ = RegisterRequestHandler(Commands.ServerInfoSet); // Open account commands - RegisterRequestHandler(Commands.AccountOpenCreate); + _ = RegisterRequestHandler(Commands.AccountOpenCreate); + // Event GM Commands + _ = RegisterRequestHandler(Commands.EPBonusSet); } private void RegisterRobotTemplates() { - _builder.Register(x => + _ = _builder.Register(x => { - var relations = x.Resolve(); - return (definition => + IRobotTemplateRelations relations = x.Resolve(); + return definition => { return relations.GetRelatedTemplateOrDefault(definition); - }); + }; }); - _builder.RegisterType().AsSelf().As(); - _builder.Register(x => + _ = _builder.RegisterType().AsSelf().As(); + _ = _builder.Register(x => { return new CachedRobotTemplateReader(x.Resolve()); }).AsSelf().As().SingleInstance().OnActivated(e => e.Instance.Init()); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance().OnActivated(e => + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => { e.Instance.Init(); }); - _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); + _ = _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); - _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); } private void RegisterTerrains() { - _builder.Register>>(x => + _ = _builder.Register>>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { + IMineralConfigurationReader reader = ctx.Resolve(); + OreNpcSpawner listener = new OreNpcSpawner(zone, ctx.Resolve(), reader); + EventListenerService eventListenerService = ctx.Resolve(); + eventListenerService.AttachListener(listener); if (zone is TrainingZone) { - var repo = ctx.Resolve(); - var config = new GravelConfiguration(zone); - var layer = new GravelLayer(zone.Size.Width, zone.Size.Height, config, repo); + GravelRepository repo = ctx.Resolve(); + GravelConfiguration config = new GravelConfiguration(zone); + GravelLayer layer = new GravelLayer(zone.Size.Width, zone.Size.Height, config, repo); layer.LoadMineralNodes(); - return new[] {layer}; + return new[] { layer }; } - var nodeGeneratorFactory = new MineralNodeGeneratorFactory(zone); - var reader = ctx.Resolve(); - var materialLayers = new List(); + MineralNodeGeneratorFactory nodeGeneratorFactory = new MineralNodeGeneratorFactory(zone); - foreach (var configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) - { - var repo = new MineralNodeRepository(zone, configuration.Type); + List materialLayers = new List(); + foreach (IMineralConfiguration configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) + { + MineralNodeRepository repo = new MineralNodeRepository(zone, configuration.Type); switch (configuration.ExtractionType) { case MineralExtractionType.Solid: - { - var layer = new OreLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory); - layer.LoadMineralNodes(); - materialLayers.Add(layer); - break; - } + { + OreLayer layer = new OreLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory, eventListenerService); + layer.LoadMineralNodes(); + materialLayers.Add(layer); + break; + } case MineralExtractionType.Liquid: - { - var layer = new LiquidLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory); - layer.LoadMineralNodes(); - materialLayers.Add(layer); - break; - } + { + LiquidLayer layer = new LiquidLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory, eventListenerService); + layer.LoadMineralNodes(); + materialLayers.Add(layer); + break; + } default: throw new ArgumentOutOfRangeException(); } @@ -2183,44 +2356,47 @@ private void RegisterTerrains() }; }); - _builder.RegisterType(); - _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterGeneric(typeof(IntervalLayerSaver<>)).InstancePerDependency(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterGeneric(typeof(IntervalLayerSaver<>)).InstancePerDependency(); - _builder.Register(x => + _ = _builder.Register(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { - var terrain = ctx.Resolve(); + Terrain terrain = ctx.Resolve(); - var size = zone.Configuration.Size; + System.Drawing.Size size = zone.Configuration.Size; - var loader = ctx.Resolve(); + ILayerFileIO loader = ctx.Resolve(); - var blocks = loader.Load(zone, LayerType.Blocks); + BlockingInfo[] blocks = loader.Load(zone, LayerType.Blocks); terrain.Blocks = new Layer(LayerType.Blocks, blocks, size.Width, size.Height); - var controls = loader.Load(zone, LayerType.Control); + TerrainControlInfo[] controls = loader.Load(zone, LayerType.Control); terrain.Controls = new Layer(LayerType.Control, controls, size.Width, size.Height); - var plants = loader.Load(zone, LayerType.Plants); + PlantInfo[] plants = loader.Load(zone, LayerType.Plants); terrain.Plants = new Layer(LayerType.Plants, plants, size.Width, size.Height); - var altitude = loader.Load(zone, LayerType.Altitude); - var altitudeLayer = new AltitudeLayer(altitude, size.Width, size.Height); + ushort[] altitude = loader.Load(zone, LayerType.Altitude); + AltitudeLayer altitudeLayer; if (zone.Configuration.Terraformable) { - var original = loader.LoadLayerData(zone, "altitude_original"); - var originalAltitude = new Layer(LayerType.OriginalAltitude, original, size.Width, size.Height); - var blend = loader.LoadLayerData(zone, "altitude_blend"); - var blendLayer = new Layer(LayerType.Blend, blend, size.Width, size.Height); - altitudeLayer = new TerraformableAltitude(originalAltitude, blendLayer, altitudeLayer.RawData); + Layer originalAltitude = new Layer(LayerType.OriginalAltitude, altitude, size.Width, size.Height); + ushort[] blend = loader.LoadLayerData(zone, "altitude_blend"); + Layer blendLayer = new Layer(LayerType.Blend, blend, size.Width, size.Height); + altitudeLayer = new TerraformableAltitude(originalAltitude, blendLayer, altitude); + } + else + { + altitudeLayer = new AltitudeLayer(altitude, size.Width, size.Height); } terrain.Altitude = altitudeLayer; @@ -2228,17 +2404,17 @@ private void RegisterTerrains() if (!zone.Configuration.Terraformable) { - var b = new PassableMapBuilder(terrain.Blocks, terrain.Slope, zone.GetPassablePositionFromDb()); + PassableMapBuilder b = new PassableMapBuilder(terrain.Blocks, terrain.Slope, zone.GetPassablePositionFromDb()); terrain.Passable = b.Build(); } terrain.Materials = ctx.Resolve>>().Invoke(zone).ToDictionary(m => m.Type); - var layerSavers = new CompositeProcess(); - layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Blocks,zone)); - layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Controls,zone)); - layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Plants,zone)); - layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Altitude,zone)); + CompositeProcess layerSavers = new CompositeProcess(); + layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Blocks, zone)); + layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Controls, zone)); + layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Plants, zone)); + layerSavers.AddProcess(ctx.Resolve.Factory>().Invoke(terrain.Altitude, zone)); ctx.Resolve().AddProcess(layerSavers.ToAsync().AsTimed(TimeSpan.FromHours(2))); ctx.Resolve().AddProcess(terrain.Materials.Values.OfType().ToCompositeProcess().ToAsync().AsTimed(TimeSpan.FromMinutes(2))); @@ -2249,68 +2425,128 @@ private void RegisterTerrains() private void RegisterIntrusions() { - RegisterRequestHandler(Commands.BaseGetOwnershipInfo); - RegisterRequestHandler(Commands.IntrusionGetPauseTime); - RegisterRequestHandler(Commands.IntrusionSetPauseTime); - RegisterRequestHandler(Commands.IntrusionUpgradeFacility); - RegisterRequestHandler(Commands.SetIntrusionSiteMessage); - RegisterRequestHandler(Commands.GetIntrusionLog); - RegisterRequestHandler(Commands.GetIntrusionStabilityLog); - RegisterRequestHandler(Commands.GetStabilityBonusThresholds); - RegisterRequestHandler(Commands.GetIntrusionSiteInfo); - RegisterRequestHandler(Commands.GetIntrusionPublicLog); - RegisterRequestHandler(Commands.GetIntrusionMySitesLog); - - RegisterZoneRequestHandler(Commands.IntrusionSAPGetItemInfo); - RegisterZoneRequestHandler(Commands.IntrusionSAPSubmitItem); - RegisterZoneRequestHandler(Commands.IntrusionSiteSetEffectBonus); - RegisterZoneRequestHandler(Commands.IntrusionSetDefenseThreshold); - - RegisterZoneRequestHandler(Commands.GetRobotFittingInfo); + _ = RegisterRequestHandler(Commands.BaseGetOwnershipInfo); + _ = RegisterRequestHandler(Commands.IntrusionGetPauseTime); + _ = RegisterRequestHandler(Commands.IntrusionSetPauseTime); + _ = RegisterRequestHandler(Commands.IntrusionUpgradeFacility); + _ = RegisterRequestHandler(Commands.SetIntrusionSiteMessage); + _ = RegisterRequestHandler(Commands.GetIntrusionLog); + _ = RegisterRequestHandler(Commands.GetIntrusionStabilityLog); + _ = RegisterRequestHandler(Commands.GetStabilityBonusThresholds); + _ = RegisterRequestHandler(Commands.GetIntrusionSiteInfo); + _ = RegisterRequestHandler(Commands.GetIntrusionPublicLog); + _ = RegisterRequestHandler(Commands.GetIntrusionMySitesLog); + + _ = RegisterZoneRequestHandler(Commands.IntrusionSAPGetItemInfo); + _ = RegisterZoneRequestHandler(Commands.IntrusionSAPSubmitItem); + _ = RegisterZoneRequestHandler(Commands.IntrusionSiteSetEffectBonus); + _ = RegisterZoneRequestHandler(Commands.IntrusionSetDefenseThreshold); + + _ = RegisterZoneRequestHandler(Commands.GetRobotFittingInfo); } private void RegisterRifts() { - _builder.Register>(x => + _ = _builder.Register>(x => { return zone => { - if (zone.Configuration.Terraformable) + return zone.Configuration.Terraformable ? new PvpRiftSpawnPositionFinder(zone) : (RiftSpawnPositionFinder)new PveRiftSpawnPositionFinder(zone); + }; + }); + + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + + _ = _builder.Register>(x => + { + IComponentContext ctx = x.Resolve(); + return zone => + { + if (zone is TrainingZone) { - return new PvpRiftSpawnPositionFinder(zone); + return null; } - return new PveRiftSpawnPositionFinder(zone); + if (zone is StrongHoldZone) + { + + int strongHoldExitConfigCount = Db.Query().CommandText("SELECT COUNT(*) FROM strongholdexitconfig WHERE zoneid = @zoneId;") + .SetParameter("@zoneId", zone.Id) + .ExecuteScalar(); + return strongHoldExitConfigCount < 1 ? null : (IRiftManager)ctx.Resolve(new TypedParameter(typeof(IZone), zone)); + } + + + List zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute(); + if (zoneConfigs.Count < 1) + { + return null; + } + + System.Data.IDataRecord record = zoneConfigs[0]; + int maxrifts = record.GetValue("maxrifts"); + + if (maxrifts < 1) + { + return null; + } + + TimeRange spawnTime = TimeRange.FromLength(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); + RiftSpawnPositionFinder finder = ctx.Resolve>().Invoke(zone); + return ctx.Resolve(new TypedParameter(typeof(IZone), zone), new NamedParameter("spawnTime", spawnTime), new NamedParameter("spawnPositionFinder", finder)); }; }); + } - _builder.RegisterType(); + private void RegisterRelics() + { + _ = _builder.RegisterType().As(); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { - if (zone is TrainingZone) + int numRelicConfigs = Db.Query().CommandText("SELECT id FROM relicspawninfo WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute().Count; + if (numRelicConfigs < 1) + { return null; + } - var spawnTime = TimeRange.FromLength(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); - var finder = ctx.Resolve>().Invoke(zone); - return ctx.Resolve(new TypedParameter(typeof(IZone),zone),new NamedParameter("spawnTime",spawnTime),new NamedParameter("spawnPositionFinder",finder)); + List zoneConfigs = Db.Query().CommandText("SELECT maxspawn FROM reliczoneconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute(); + if (zoneConfigs.Count < 1) + { + return null; + } + System.Data.IDataRecord record = zoneConfigs[0]; + int maxspawn = record.GetValue("maxspawn"); + if (maxspawn < 1) + { + return null; + } + //Do not register RelicManagers on zones without the necessary valid entries in reliczoneconfig and relicspawninfo + return ctx.Resolve(new TypedParameter(typeof(IZone), zone)); }; }); } private void RegisterZones() { - _builder.RegisterType().AsSelf().As(); + _ = _builder.RegisterType().AsSelf().As(); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.Register(c => + _ = _builder.Register(c => { return new WeatherService(new TimeRange(TimeSpan.FromMinutes(30), TimeSpan.FromHours(1))); }).OnActivated(e => @@ -2318,29 +2554,52 @@ private void RegisterZones() e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5))); }).As(); - _builder.RegisterType().AsSelf().As(); - _builder.RegisterType().AsSelf().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.Register>(x => + { + IComponentContext ctx = x.Resolve(); + return zone => + { + return new WeatherEventListener(ctx.Resolve(), zone); + }; + }); + + _ = _builder.Register>(x => + { + IComponentContext ctx = x.Resolve(); + return zone => + { + EnvironmentalEffectHandler listener = new EnvironmentalEffectHandler(zone); + ctx.Resolve().AttachListener(listener); + return listener; + }; + }); + + _ = _builder.RegisterType().AsSelf().As(); + _ = _builder.RegisterType().AsSelf().As(); - _builder.Register(x => + _ = _builder.Register(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { return new ZoneUnitService { - DefaultRepository = ctx.Resolve(new TypedParameter(typeof(IZone),zone)), - UserRepository = ctx.Resolve(new TypedParameter(typeof(IZone),zone)) + DefaultRepository = ctx.Resolve(new TypedParameter(typeof(IZone), zone)), + UserRepository = ctx.Resolve(new TypedParameter(typeof(IZone), zone)) }; }; }); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance(); - void RegisterZone(ZoneType type) where T:Zone + void RegisterZone(ZoneType type) where T : Zone { - _builder.RegisterType().Keyed(type).OnActivated(e => + _ = _builder.RegisterType().Keyed(type).OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync()); }); @@ -2349,19 +2608,32 @@ void RegisterZone(ZoneType type) where T:Zone RegisterZone(ZoneType.Pve); RegisterZone(ZoneType.Pvp); RegisterZone(ZoneType.Training); + RegisterZone(ZoneType.Stronghold); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As(); + + + _ = _builder.Register>(x => + { + IComponentContext ctx = x.Resolve(); + return zone => + { + return new StrongholdPlayerStateManager(zone, ctx.Resolve()); + }; + }); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return configuration => { - var zone = ctx.ResolveKeyed(configuration.Type); + Zone zone = ctx.ResolveKeyed(configuration.Type); zone.Configuration = configuration; zone.Listener = new TcpListener(new IPEndPoint(IPAddress.Any, configuration.ListenerPort)); + zone.ZoneEffectHandler = ctx.Resolve>().Invoke(zone); zone.UnitService = ctx.Resolve().Invoke(zone); zone.Weather = ctx.Resolve(); zone.Beams = ctx.Resolve(); @@ -2369,15 +2641,17 @@ void RegisterZone(ZoneType type) where T:Zone zone.PlantHandler = ctx.Resolve().Invoke(zone); zone.CorporationHandler = ctx.Resolve().Invoke(zone); zone.MiningLogHandler = ctx.Resolve().Invoke(zone); - zone.RiftManager = ctx.Resolve>().Invoke(zone); + zone.HarvestLogHandler = ctx.Resolve().Invoke(zone); + zone.RiftManager = ctx.Resolve>().Invoke(zone); zone.ChatLogger = ctx.Resolve().Invoke("zone", zone.Configuration.Name); zone.EnterQueueService = ctx.Resolve().Invoke(zone); zone.Terrain = ctx.Resolve().Invoke(zone); zone.PresenceManager = ctx.Resolve>().Invoke(zone); - zone.DecorHandler = ctx.Resolve(new TypedParameter(typeof(IZone),zone)); + zone.DecorHandler = ctx.Resolve(new TypedParameter(typeof(IZone), zone)); zone.Environment = ctx.Resolve(new TypedParameter(typeof(IZone), zone)); zone.SafeSpawnPoints = ctx.Resolve(new TypedParameter(typeof(IZone), zone)); zone.ZoneSessionFactory = ctx.Resolve(); + zone.RelicManager = ctx.Resolve>().Invoke(zone); if (configuration.Terraformable) { @@ -2385,18 +2659,29 @@ void RegisterZone(ZoneType type) where T:Zone zone.TerraformHandler = ctx.Resolve().Invoke(zone); } + if (configuration.Type == ZoneType.Stronghold) + { + zone.PlayerStateManager = ctx.Resolve>().Invoke(zone); + } + + ctx.Resolve().AttachListener(new NpcReinforcementSpawner(zone, ctx.Resolve())); + WeatherEventListener listener = ctx.Resolve>().Invoke(zone); + listener.Subscribe(zone.Weather); + zone.LoadUnits(); return zone; }; }); - _builder.Register(c => c.Resolve()).As(); - _builder.RegisterType().OnActivated(e => + _ = _builder.Register(c => c.Resolve()).As(); + _ = _builder.RegisterType().OnActivated(e => { - foreach (var c in e.Context.Resolve().GetAll()) + foreach (ZoneConfiguration c in e.Context.Resolve().GetAll()) { - var zoneFactory = e.Context.Resolve>(); - var zone = zoneFactory(c); + Func zoneFactory = e.Context.Resolve>(); + IZone zone = zoneFactory(c); + + _ = e.Context.Resolve>().Invoke(zone); Logger.Info("------------------"); Logger.Info("--"); @@ -2405,48 +2690,49 @@ void RegisterZone(ZoneType type) where T:Zone Logger.Info("------------------"); e.Instance.Zones.Add(zone); - }; - + } }).SingleInstance(); - _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().OnActivated(e => { - var pm = e.Context.Resolve(); + IProcessManager pm = e.Context.Resolve(); pm.AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(1))); }).As().InstancePerDependency(); - _builder.RegisterType().OnActivated(e => e.Instance.Initialize()).InstancePerDependency(); - _builder.RegisterType(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().OnActivated(e => e.Instance.Initialize()).InstancePerDependency(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().OnActivated(e => { - var pm = e.Context.Resolve(); + IProcessManager pm = e.Context.Resolve(); pm.AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(5))); }).As().InstancePerDependency(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType().OnActivated(e => + _ = _builder.RegisterType().OnActivated(e => { - var pm = e.Context.Resolve(); + IProcessManager pm = e.Context.Resolve(); pm.AddProcess(e.Instance.AsTimed(TimeSpan.FromMilliseconds(PBSHighwayHandler.DRAW_INTERVAL)).ToAsync()); }); - _builder.RegisterType(); - _builder.RegisterType().SingleInstance(); - _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().SingleInstance(); + _ = _builder.RegisterType().As(); - _builder.RegisterType() + _ = _builder.RegisterType() .As() .PropertiesAutowired() .SingleInstance(); - _builder.RegisterType().SingleInstance().As(); + _ = _builder.RegisterType().SingleInstance().As(); } private IRegistrationBuilder @@ -2457,141 +2743,149 @@ private IRegistrationBuilder(Commands.TeleportGetChannelList); - RegisterZoneRequestHandler(Commands.TeleportToZoneObject); - RegisterZoneRequestHandler(Commands.TeleportUse); - RegisterZoneRequestHandler(Commands.TeleportQueryWorldChannels); - RegisterZoneRequestHandler(Commands.JumpAnywhere); - RegisterZoneRequestHandler(Commands.ZoneDrawStatMap); - RegisterZoneRequestHandler(Commands.MissionStartFromZone); - RegisterZoneRequestHandler(Commands.ItemShopBuy); - RegisterZoneRequestHandler(Commands.ItemShopList); - RegisterZoneRequestHandler(Commands.ZoneMoveUnit); - RegisterZoneRequestHandler(Commands.ZoneGetQueueInfo); - RegisterZoneRequestHandler(Commands.ZoneSetQueueLength); - RegisterZoneRequestHandler(Commands.ZoneCancelEnterQueue); - RegisterZoneRequestHandler(Commands.ZoneGetBuildings); - - RegisterZoneRequestHandler(Commands.Dock); - - RegisterZoneRequestHandler(Commands.ZoneDecorAdd); - RegisterZoneRequestHandler(Commands.ZoneDecorSet); - RegisterZoneRequestHandler(Commands.ZoneDecorDelete); - RegisterZoneRequestHandler(Commands.ZoneDecorLock); - RegisterZoneRequestHandler(Commands.ZoneDrawDecorEnvironment); - RegisterZoneRequestHandler(Commands.ZoneSampleDecorEnvironment); - RegisterZoneRequestHandler(Commands.ZoneDrawDecorEnvByDef); - RegisterZoneRequestHandler(Commands.ZoneDrawAllDecors); - RegisterZoneRequestHandler(Commands.ZoneEnvironmentDescriptionList); - RegisterZoneRequestHandler(Commands.ZoneSampleEnvironment); - RegisterZoneRequestHandler(Commands.ZoneCreateTeleportColumn); - - RegisterZoneRequestHandler(Commands.PackItems); - RegisterZoneRequestHandler(Commands.UnpackItems); - RegisterZoneRequestHandler(Commands.TrashItems); - RegisterZoneRequestHandler(Commands.RelocateItems); - RegisterZoneRequestHandler(Commands.StackItems); - RegisterZoneRequestHandler(Commands.StackSelection); - RegisterZoneRequestHandler(Commands.UnstackAmount); - RegisterZoneRequestHandler(Commands.SetItemName); - RegisterZoneRequestHandler(Commands.ListContainer); - RegisterZoneRequestHandler(Commands.EquipModule); - RegisterZoneRequestHandler(Commands.RemoveModule); - RegisterZoneRequestHandler(Commands.ChangeModule); - RegisterZoneRequestHandler(Commands.EquipAmmo); - RegisterZoneRequestHandler(Commands.UnequipAmmo); - RegisterZoneRequestHandler(Commands.ChangeAmmo); - - RegisterZoneRequestHandler(Commands.MissionGetSupply); - RegisterZoneRequestHandler(Commands.MissionSpotPlace); - RegisterZoneRequestHandler(Commands.MissionSpotUpdate); - RegisterZoneRequestHandler(Commands.ZoneUpdateStructure); - RegisterZoneRequestHandler(Commands.RemoveMissionStructure); - RegisterZoneRequestHandler(Commands.KioskInfo); - RegisterZoneRequestHandler(Commands.KioskSubmitItem); - RegisterZoneRequestHandler(Commands.AlarmStart); - RegisterZoneRequestHandler(Commands.TriggerMissionStructure); - - RegisterZoneRequestHandler(Commands.ZoneUploadScanResult); + _ = RegisterZoneRequestHandler(Commands.TeleportGetChannelList); + _ = RegisterZoneRequestHandler(Commands.TeleportToZoneObject); + _ = RegisterZoneRequestHandler(Commands.TeleportUse); + _ = RegisterZoneRequestHandler(Commands.TeleportQueryWorldChannels); + _ = RegisterZoneRequestHandler(Commands.JumpAnywhere); + _ = RegisterZoneRequestHandler(Commands.MovePlayer); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawStatMap); + _ = RegisterZoneRequestHandler(Commands.MissionStartFromZone); + _ = RegisterZoneRequestHandler(Commands.ItemShopBuy); + _ = RegisterZoneRequestHandler(Commands.ItemShopList); + _ = RegisterZoneRequestHandler(Commands.ZoneMoveUnit); + _ = RegisterZoneRequestHandler(Commands.ZoneGetQueueInfo); + _ = RegisterZoneRequestHandler(Commands.ZoneSetQueueLength); + _ = RegisterZoneRequestHandler(Commands.ZoneCancelEnterQueue); + _ = RegisterZoneRequestHandler(Commands.ZoneGetBuildings); + + _ = RegisterZoneRequestHandler(Commands.Dock); + + _ = RegisterZoneRequestHandler(Commands.ZoneDecorAdd); + _ = RegisterZoneRequestHandler(Commands.ZoneDecorSet); + _ = RegisterZoneRequestHandler(Commands.ZoneDecorDelete); + _ = RegisterZoneRequestHandler(Commands.ZoneDecorLock); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawDecorEnvironment); + _ = RegisterZoneRequestHandler(Commands.ZoneSampleDecorEnvironment); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawDecorEnvByDef); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawAllDecors); + _ = RegisterZoneRequestHandler(Commands.ZoneEnvironmentDescriptionList); + _ = RegisterZoneRequestHandler(Commands.ZoneSampleEnvironment); + _ = RegisterZoneRequestHandler(Commands.ZoneCreateTeleportColumn); + + _ = RegisterZoneRequestHandler(Commands.PackItems); + _ = RegisterZoneRequestHandler(Commands.UnpackItems); + _ = RegisterZoneRequestHandler(Commands.TrashItems); + _ = RegisterZoneRequestHandler(Commands.RelocateItems); + _ = RegisterZoneRequestHandler(Commands.StackItems); + _ = RegisterZoneRequestHandler(Commands.StackSelection); + _ = RegisterZoneRequestHandler(Commands.UnstackAmount); + _ = RegisterZoneRequestHandler(Commands.SetItemName); + _ = RegisterZoneRequestHandler(Commands.ListContainer); + _ = RegisterZoneRequestHandler(Commands.EquipModule); + _ = RegisterZoneRequestHandler(Commands.RemoveModule); + _ = RegisterZoneRequestHandler(Commands.ChangeModule); + _ = RegisterZoneRequestHandler(Commands.EquipAmmo); + _ = RegisterZoneRequestHandler(Commands.UnequipAmmo); + _ = RegisterZoneRequestHandler(Commands.ChangeAmmo); + + _ = RegisterZoneRequestHandler(Commands.MissionGetSupply); + _ = RegisterZoneRequestHandler(Commands.MissionSpotPlace); + _ = RegisterZoneRequestHandler(Commands.MissionSpotUpdate); + _ = RegisterZoneRequestHandler(Commands.ZoneUpdateStructure); + _ = RegisterZoneRequestHandler(Commands.RemoveMissionStructure); + _ = RegisterZoneRequestHandler(Commands.KioskInfo); + _ = RegisterZoneRequestHandler(Commands.KioskSubmitItem); + _ = RegisterZoneRequestHandler(Commands.AlarmStart); + _ = RegisterZoneRequestHandler(Commands.TriggerMissionStructure); + + _ = RegisterZoneRequestHandler(Commands.ZoneUploadScanResult); //admin - RegisterZoneRequestHandler(Commands.ZoneEntityChangeState); - RegisterZoneRequestHandler(Commands.ZoneRemoveByDefinition); - RegisterZoneRequestHandler(Commands.ZoneMakeGotoXY); - RegisterZoneRequestHandler(Commands.ZoneDrawBeam); - RegisterZoneRequestHandler(Commands.ZoneSetRuntimeZoneEntityName); - RegisterZoneRequestHandler(Commands.ZoneCheckRoaming); - RegisterZoneRequestHandler(Commands.ZonePBSTest); - RegisterZoneRequestHandler(Commands.ZonePBSFixOrphaned); - RegisterZoneRequestHandler(Commands.ZoneFixPBS); - RegisterZoneRequestHandler(Commands.ZoneServerMessage); - RegisterZoneRequestHandler(Commands.ZonePlaceWall); - RegisterZoneRequestHandler(Commands.ZoneClearWalls); - RegisterZoneRequestHandler(Commands.ZoneHealAllWalls); - RegisterZoneRequestHandler(Commands.ZoneTerraformTest); - RegisterZoneRequestHandler(Commands.ZoneForceDeconstruct); - RegisterZoneRequestHandler(Commands.ZoneSetReinforceCounter); - RegisterZoneRequestHandler(Commands.ZoneRestoreOriginalGamma); - RegisterZoneRequestHandler(Commands.ZoneSwitchDegrade); - RegisterZoneRequestHandler(Commands.ZoneKillNPlants); - RegisterZoneRequestHandler(Commands.ZoneDisplayMissionRandomPoints); - RegisterZoneRequestHandler(Commands.ZoneDisplayMissionSpots); - RegisterZoneRequestHandler(Commands.NpcCheckCondition); - RegisterZoneRequestHandler(Commands.ZoneClearLayer); - RegisterZoneRequestHandler(Commands.ZonePutPlant); - RegisterZoneRequestHandler(Commands.ZoneSetPlantsSpeed); - RegisterZoneRequestHandler(Commands.ZoneGetPlantsMode); - RegisterZoneRequestHandler(Commands.ZoneSetPlantsMode); - RegisterZoneRequestHandler(Commands.ZoneCreateGarden); - RegisterZoneRequestHandler(Commands.ZoneCreateIsland); - RegisterZoneRequestHandler(Commands.ZoneDrawBlockingByDefinition); - RegisterZoneRequestHandler(Commands.ZoneCleanBlockingByDefinition); - RegisterZoneRequestHandler(Commands.ZoneCleanObstacleBlocking); - - - - RegisterZoneRequestHandler(Commands.NpcListSafeSpawnPoint); - RegisterZoneRequestHandler(Commands.NpcPlaceSafeSpawnPoint); - RegisterZoneRequestHandler(Commands.NpcAddSafeSpawnPoint); - RegisterZoneRequestHandler(Commands.NpcSetSafeSpawnPoint); - RegisterZoneRequestHandler(Commands.NpcDeleteSafeSpawnPoint); - RegisterZoneRequestHandler(Commands.ZoneListPresences); - RegisterZoneRequestHandler(Commands.ZoneNpcFlockNew); - RegisterZoneRequestHandler(Commands.ZoneNpcFlockSet); - RegisterZoneRequestHandler(Commands.ZoneNpcFlockDelete); - RegisterZoneRequestHandler(Commands.ZoneNpcFlockKill); - RegisterZoneRequestHandler(Commands.ZoneNpcFlockSetParameter); - - RegisterZoneRequestHandler(Commands.GetRifts); - RegisterZoneRequestHandler(Commands.UseItem); - RegisterZoneRequestHandler(Commands.GateSetName); - RegisterZoneRequestHandler(Commands.ProximityProbeRemove); - RegisterZoneRequestHandler(Commands.FieldTerminalInfo); - - RegisterZoneRequestHandler(Commands.PBSFeedableInfo); - RegisterZoneRequestHandler(Commands.PBSFeedItems); - RegisterZoneRequestHandler(Commands.PBSMakeConnection); - RegisterZoneRequestHandler(Commands.PBSBreakConnection); - RegisterZoneRequestHandler(Commands.PBSRenameNode); - RegisterZoneRequestHandler(Commands.PBSSetConnectionWeight); - RegisterZoneRequestHandler(Commands.PBSSetOnline); - RegisterZoneRequestHandler(Commands.PBSGetNetwork); - RegisterZoneRequestHandler(Commands.PBSCheckDeployment); - RegisterZoneRequestHandler(Commands.PBSSetStandingLimit); - RegisterZoneRequestHandler(Commands.PBSNodeInfo); - RegisterZoneRequestHandler(Commands.PBSGetTerritories); - RegisterZoneRequestHandler(Commands.PBSSetTerritoryVisibility); - RegisterZoneRequestHandler(Commands.PBSSetBaseDeconstruct); - RegisterZoneRequestHandler(Commands.PBSSetReinforceOffset); - RegisterZoneRequestHandler(Commands.PBSSetEffect); + _ = RegisterZoneRequestHandler(Commands.ZoneEntityChangeState); + _ = RegisterZoneRequestHandler(Commands.ZoneRemoveByDefinition); + _ = RegisterZoneRequestHandler(Commands.ZoneMakeGotoXY); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawBeam); + _ = RegisterZoneRequestHandler(Commands.ZoneSetRuntimeZoneEntityName); + _ = RegisterZoneRequestHandler(Commands.ZoneCheckRoaming); + _ = RegisterZoneRequestHandler(Commands.ZonePBSTest); + _ = RegisterZoneRequestHandler(Commands.ZonePBSFixOrphaned); + _ = RegisterZoneRequestHandler(Commands.ZoneFixPBS); + _ = RegisterZoneRequestHandler(Commands.ZoneServerMessage); + _ = RegisterZoneRequestHandler(Commands.ZonePlaceWall); + _ = RegisterZoneRequestHandler(Commands.ZoneClearWalls); + _ = RegisterZoneRequestHandler(Commands.ZoneHealAllWalls); + _ = RegisterZoneRequestHandler(Commands.ZoneTerraformTest); + _ = RegisterZoneRequestHandler(Commands.ZoneForceDeconstruct); + _ = RegisterZoneRequestHandler(Commands.ZoneSetReinforceCounter); + _ = RegisterZoneRequestHandler(Commands.ZoneRestoreOriginalGamma); + _ = RegisterZoneRequestHandler(Commands.ZoneSwitchDegrade); + _ = RegisterZoneRequestHandler(Commands.ZoneKillNPlants); + _ = RegisterZoneRequestHandler(Commands.ZoneDisplayMissionRandomPoints); + _ = RegisterZoneRequestHandler(Commands.ZoneDisplayMissionSpots); + _ = RegisterZoneRequestHandler(Commands.NpcCheckCondition); + _ = RegisterZoneRequestHandler(Commands.ZoneClearLayer); + _ = RegisterZoneRequestHandler(Commands.ZonePutPlant); + _ = RegisterZoneRequestHandler(Commands.ZoneSetPlantsSpeed); + _ = RegisterZoneRequestHandler(Commands.ZoneGetPlantsMode); + _ = RegisterZoneRequestHandler(Commands.ZoneSetPlantsMode); + _ = RegisterZoneRequestHandler(Commands.ZoneCreateGarden); + _ = RegisterZoneRequestHandler(Commands.ZoneCreateIsland); + _ = RegisterZoneRequestHandler(Commands.ZoneCreateTerraformLimit); + _ = RegisterZoneRequestHandler(Commands.ZoneSetLayerWithBitMap); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawBlockingByDefinition); + _ = RegisterZoneRequestHandler(Commands.ZoneCleanBlockingByDefinition); + _ = RegisterZoneRequestHandler(Commands.ZoneCleanObstacleBlocking); + _ = RegisterZoneRequestHandler(Commands.ZoneFillGroundTypeRandom); + + + + _ = RegisterZoneRequestHandler(Commands.NpcListSafeSpawnPoint); + _ = RegisterZoneRequestHandler(Commands.NpcPlaceSafeSpawnPoint); + _ = RegisterZoneRequestHandler(Commands.NpcAddSafeSpawnPoint); + _ = RegisterZoneRequestHandler(Commands.NpcSetSafeSpawnPoint); + _ = RegisterZoneRequestHandler(Commands.NpcDeleteSafeSpawnPoint); + _ = RegisterZoneRequestHandler(Commands.ZoneListPresences); + _ = RegisterZoneRequestHandler(Commands.ZoneNpcFlockNew); + _ = RegisterZoneRequestHandler(Commands.ZoneNpcFlockSet); + _ = RegisterZoneRequestHandler(Commands.ZoneNpcFlockDelete); + _ = RegisterZoneRequestHandler(Commands.ZoneNpcFlockKill); + _ = RegisterZoneRequestHandler(Commands.ZoneNpcFlockSetParameter); + + _ = RegisterZoneRequestHandler(Commands.GetRifts); + _ = RegisterZoneRequestHandler(Commands.UseItem); + _ = RegisterZoneRequestHandler(Commands.GateSetName); + + _ = RegisterZoneRequestHandler(Commands.ProximityProbeRemove); + + _ = RegisterZoneRequestHandler(Commands.FieldTerminalInfo); + + _ = RegisterZoneRequestHandler(Commands.PBSFeedableInfo); + _ = RegisterZoneRequestHandler(Commands.PBSFeedItems); + _ = RegisterZoneRequestHandler(Commands.PBSMakeConnection); + _ = RegisterZoneRequestHandler(Commands.PBSBreakConnection); + _ = RegisterZoneRequestHandler(Commands.PBSRenameNode); + _ = RegisterZoneRequestHandler(Commands.PBSSetConnectionWeight); + _ = RegisterZoneRequestHandler(Commands.PBSSetOnline); + _ = RegisterZoneRequestHandler(Commands.PBSGetNetwork); + _ = RegisterZoneRequestHandler(Commands.PBSCheckDeployment); + _ = RegisterZoneRequestHandler(Commands.PBSSetStandingLimit); + _ = RegisterZoneRequestHandler(Commands.PBSNodeInfo); + _ = RegisterZoneRequestHandler(Commands.PBSGetTerritories); + _ = RegisterZoneRequestHandler(Commands.PBSSetTerritoryVisibility); + _ = RegisterZoneRequestHandler(Commands.PBSSetBaseDeconstruct); + _ = RegisterZoneRequestHandler(Commands.PBSSetReinforceOffset); + _ = RegisterZoneRequestHandler(Commands.PBSSetEffect); + _ = RegisterZoneRequestHandler(Commands.ZoneDrawRamp); + _ = RegisterZoneRequestHandler(Commands.ZoneSmooth); } private void RegisterPBS() { - _builder.RegisterGeneric(typeof(PBSObjectHelper<>)); - _builder.RegisterGeneric(typeof(PBSReinforceHandler<>)); - _builder.RegisterType(); + _ = _builder.RegisterGeneric(typeof(PBSObjectHelper<>)); + _ = _builder.RegisterGeneric(typeof(PBSReinforceHandler<>)); + _ = _builder.RegisterType(); } } } \ No newline at end of file diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 9615ea4da..1797a52a1 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -5,419 +5,440 @@ namespace Perpetuum.ExportedTypes { - public enum AggregateField - { - undefined = 0, - accuracy = 1, - accuracy_modifier = 2, - aggrorange = 7, - aggrorange_modifier = 8, - ammo_reload_time = 9, - ammo_reload_time_modifier = 10, - aoe_explosion_radius = 12, - aoe_explosion_radius_modifier = 13, - armor_current = 575, - armor_max = 16, - armor_max_modifier = 17, - armor_repair_amount = 19, - armor_repair_amount_modifier = 20, - armor_repair_cycle_time_modifier = 22, - armor_repair_optimal_range_modifier = 23, - blob_effect = 595, - blob_emission = 587, - blob_emission_modifier = 590, - blob_emission_modifier_modifier = 598, - blob_emission_radius = 588, - blob_emission_radius_modifier = 591, - blob_level_high = 593, - blob_level_high_modifier = 594, - blob_level_low = 589, - blob_level_low_modifier = 592, - chemical_damage_to_core_modifier = 621, - construction_charge_amount = 647, - control_speed_boost_time_add = 505, - core_added = 24, - core_current = 574, - core_max = 26, - core_max_modifier = 27, - core_recharge_time = 29, - core_recharge_time_modifier = 30, - core_usage = 32, - core_usage_armor_hardener_modifier = 33, - core_usage_armor_repair_modifier = 34, - core_usage_cargo_scanner_modifier = 35, - core_usage_chassis_scanner_modifier = 36, - core_usage_cloaking_modifier = 37, - core_usage_detection_modul_modifier = 610, - core_usage_drillers_modifier = 38, - core_usage_energy_neutralizers_modifier = 39, - core_usage_energy_transfers_modifier = 40, - core_usage_energy_vampires_modifier = 41, - core_usage_energytorpedo_modifier = 42, - core_usage_gang_assist_module_modifier = 658, - core_usage_harvester_modifier = 43, - core_usage_industrial_aoe_modifier = 44, - core_usage_industrial_scanner_modifier = 45, - core_usage_ioncannon_modifier = 46, - core_usage_laser_modifier = 47, - core_usage_missile_modifier = 657, - core_usage_plasma_modifier = 48, - core_usage_projectile_modifier = 656, - core_usage_railgun_modifier = 49, - core_usage_remote_armor_repair_modifier = 50, - core_usage_remote_sensor_booster_modifier = 51, - core_usage_sensor_booster_modifier = 53, - core_usage_sensor_dampener_modifier = 54, - core_usage_sensor_jammer_modifier = 55, - core_usage_shield_generator_modifier = 56, - core_usage_shield_hardener_modifier = 57, - core_usage_stealth_modul_modifier = 612, - core_usage_terraformer_modifier = 58, - core_usage_weapon_modifier = 59, - core_usage_webber_modifier = 60, - cpu_current = 61, - cpu_max = 62, - cpu_max_modifier = 63, - cpu_usage = 64, - cpu_usage_armor_modifier = 65, - cpu_usage_detection_modul_modifier = 609, - cpu_usage_engineering_modifier = 66, - cpu_usage_ew_modifier = 67, - cpu_usage_gathering_modifier = 68, - cpu_usage_large_energy_neutralizer_modifier = 69, - cpu_usage_large_energy_transfer_modifier = 70, - cpu_usage_large_energy_vampire_modifier = 71, - cpu_usage_large_energytorpedo_modifier = 72, - cpu_usage_large_industrial_aoe_modifier = 73, - cpu_usage_large_ioncannon_modifier = 74, - cpu_usage_large_laser_modifier = 75, - cpu_usage_large_missile_modifier = 76, - cpu_usage_large_plasma_modifier = 77, - cpu_usage_large_projectile_modifier = 78, - cpu_usage_large_railgun_modifier = 79, - cpu_usage_medium_energy_neutralizer_modifier = 80, - cpu_usage_medium_energy_transfer_modifier = 81, - cpu_usage_medium_energy_vampire_modifier = 82, - cpu_usage_medium_energytorpedo_modifier = 83, - cpu_usage_medium_industrial_aoe_modifier = 84, - cpu_usage_medium_ioncannon_modifier = 85, - cpu_usage_medium_laser_modifier = 86, - cpu_usage_medium_missile_modifier = 87, - cpu_usage_medium_plasma_modifier = 88, - cpu_usage_medium_projectile_modifier = 89, - cpu_usage_medium_railgun_modifier = 90, - cpu_usage_mining_modifier = 91, - cpu_usage_remote_sensor_booster_modifier = 92, - cpu_usage_sensor_booster_modifier = 94, - cpu_usage_shield_generators_modifier = 581, - cpu_usage_small_energy_neutralizer_modifier = 95, - cpu_usage_small_energy_transfer_modifier = 96, - cpu_usage_small_energy_vampire_modifier = 97, - cpu_usage_small_energytorpedo_modifier = 98, - cpu_usage_small_industrial_aoe_modifier = 99, - cpu_usage_small_ioncannon_modifier = 100, - cpu_usage_small_laser_modifier = 101, - cpu_usage_small_missile_modifier = 102, - cpu_usage_small_plasma_modifier = 103, - cpu_usage_small_projectile_modifier = 104, - cpu_usage_small_railgun_modifier = 105, - cpu_usage_stealth_modul_modifier = 611, - cpu_usage_weapons_modifier = 106, - critical_damage_modifier = 645, - critical_damage_modifier_modifier = 646, - critical_hit_chance = 107, - critical_hit_chance_modifier = 108, - cycle_time = 110, - damage_chemical = 112, - damage_energytorpedo_modifier = 114, - damage_explosive = 115, - damage_industrial_aoe_modifier = 117, - damage_ioncannon_modifier = 122, - damage_kinetic = 123, - damage_large_energytorpedo_modifier = 125, - damage_large_industrial_aoe_modifier = 126, - damage_large_ioncannon_modifier = 127, - damage_large_laser_modifier = 128, - damage_large_missile_modifier = 129, - damage_large_plasma_modifier = 130, - damage_large_projectile_modifier = 131, - damage_large_railgun_modifier = 132, - damage_laser_modifier = 133, - damage_medium_energytorpedo_modifier = 134, - damage_medium_industrial_aoe_modifier = 135, - damage_medium_ioncannon_modifier = 136, - damage_medium_laser_modifier = 137, - damage_medium_missile_modifier = 138, - damage_medium_plasma_modifier = 139, - damage_medium_projectile_modifier = 140, - damage_medium_railgun_modifier = 141, - damage_missile_modifier = 142, - damage_modifier = 143, - damage_plasma_modifier = 145, - damage_projectile_modifier = 146, - damage_railgun_modifier = 147, - damage_small_energytorpedo_modifier = 148, - damage_small_industrial_aoe_modifier = 149, - damage_small_ioncannon_modifier = 150, - damage_small_laser_modifier = 151, - damage_small_missile_modifier = 152, - damage_small_plasma_modifier = 153, - damage_small_projectile_modifier = 154, - damage_small_railgun_modifier = 155, - damage_thermal = 156, - damage_turret_modifier = 158, - decay_chance = 613, - default_effect_range = 585, - despawn_time = 580, - detection_range = 599, - detection_range_modifier = 600, - detection_strength = 601, - detection_strength_modifier = 602, - devastate_resilience = 510, - devastate_resilience_modifier = 511, - devastating_chance = 507, - devastating_chance_modifier = 508, - ecm_strength = 162, - ecm_strength_modifier = 163, - effect_armor_max_modifier = 165, - effect_aura_radius = 536, - effect_aura_radius_modifier = 537, - effect_blob_emission_modifier = 596, - effect_blob_emission_radius_modifier = 597, - effect_core_recharge_time_modifier = 166, - effect_core_usage_gathering_modifier = 530, - effect_critical_hit_chance_modifier = 533, - effect_detection_strength_modifier = 605, - effect_devastate_resilience_modifier = 535, - effect_devastating_chance_modifier = 534, - effect_enhancer_aura_radius_modifier = 539, - effect_enhancer_detection_strength_modifier = 606, - effect_enhancer_lock_resist_modifier = 630, - effect_enhancer_massiveness_modifier = 632, - effect_enhancer_sensor_booster_locking_range_modifier = 167, - effect_enhancer_sensor_booster_locking_time_modifier = 168, - effect_enhancer_sensor_dampener_locking_range_modifier = 169, - effect_enhancer_sensor_dampener_locking_time_modifier = 170, - effect_enhancer_sensor_strength_modifier = 631, - effect_enhancer_speed_max_modifier = 171, - effect_enhancer_stealth_strength_modifier = 608, - effect_ew_optimal_range_modifier = 529, - effect_gathering_cycle_time_modifier = 544, - effect_harvesting_amount_modifier = 636, - effect_harvesting_cycle_time_modifier = 172, - effect_lock_resist = 629, - effect_locking_range_modifier = 173, - effect_locking_time_modifier = 531, - effect_massiveness = 625, - effect_massivness_speed_max_modifier = 623, - effect_mining_amount_modifier = 635, - effect_mining_cycle_time_modifier = 174, - effect_mining_probe_accuracy_modifier = 637, - effect_missile_range_modifier = 653, - effect_optimal_range_modifier = 175, - effect_repair_amount_modifier = 532, - effect_resist_chemical = 176, - effect_resist_explosive = 177, - effect_resist_kinetic = 178, - effect_resist_thermal = 179, - effect_sensor_booster_locking_range_modifier = 180, - effect_sensor_booster_locking_time_modifier = 181, - effect_sensor_dampener_locking_range_modifier = 182, - effect_sensor_dampener_locking_time_modifier = 183, - effect_sensor_strength_modifier = 626, - effect_shield_absorbtion_modifier = 184, - effect_signature_radius_modifier = 185, - effect_speed_highway_modifier = 622, - effect_speed_max_add = 503, - effect_speed_max_modifier = 186, - effect_stealth_strength_modifier = 607, - effect_weapon_cycle_time_modifier = 528, - energy_dispersion = 545, - energy_dispersion_modifier = 546, - energy_neutralized_amount = 187, - energy_neutralized_amount_modifier = 188, - energy_neutralizer_optimal_range_modifier = 190, - energy_transfer_amount = 191, - energy_transfer_amount_modifier = 192, - energy_transferer_optimal_range_modifier = 194, - energy_vampire_optimal_range_modifier = 195, - energy_vampired_amount = 196, - energy_vampired_amount_modifier = 197, - energytorpedo_cycle_time_modifier = 199, - energytorpedo_falloff_modifier = 200, - energytorpedo_optimal_range_modifier = 201, - engineering_cycle_time_modifier = 202, - engineering_optimal_range_modifier = 624, - ew_optimal_range_modifier = 203, - explosion_radius = 204, - explosion_radius_modifier = 205, - explosive_damage_to_core_modifier = 620, - falloff = 207, - falloff_modifier = 208, - gang_effect_enhancer_armor_max_modifier = 516, - gang_effect_enhancer_core_recharge_time_modifier = 523, - gang_effect_enhancer_core_usage_gathering_modifier = 517, - gang_effect_enhancer_critical_hit_chance_modifier = 522, - gang_effect_enhancer_devastating_chance_modifier = 526, - gang_effect_enhancer_ew_optimal_range_modifier = 515, - gang_effect_enhancer_gathering_cycle_time_modifier = 524, - gang_effect_enhancer_locking_range_modifier = 518, - gang_effect_enhancer_locking_time_modifier = 519, - gang_effect_enhancer_repair_amount_modifier = 521, - gang_effect_enhancer_resilience_modifier = 527, - gang_effect_enhancer_shield_absorbtion_ratio_modifier = 525, - gang_effect_enhancer_signature_radius_modifier = 520, - gang_effect_enhancer_speed_max_modifier = 513, - gang_effect_enhancer_weapon_cycle_time_modifier = 514, - gathering_cycle_time_modifier = 542, - harvesting_amount_electroplant_modifier = 614, - harvesting_amount_helioptris_modifier = 210, - harvesting_amount_modifier = 211, - harvesting_amount_prismocitae_modifier = 212, - harvesting_amount_triandlus_modifier = 214, - harvesting_cycle_time_modifier = 215, - industrial_aoe_cycle_time_modifier = 216, - industrial_aoe_optimal_range_modifier_modifier = 217, - ioncannon_cycle_time_modifier = 218, - ioncannon_falloff_modifier = 219, - ioncannon_optimal_range_modifier = 220, - kinetic_damage_to_core_modifier = 618, - laser_ammo_cycle_time_modifier = 640, - laser_cycle_time_modifier = 221, - laser_falloff_modifier = 222, - laser_optimal_range_modifier = 223, - least_optimal = 338, - least_optimal_modifier = 340, - lock_resist = 627, - lock_resist_modifier = 628, - locked_targets_current = 224, - locked_targets_max = 225, - locked_targets_max_bonus = 226, - locking_range = 228, - locking_range_modifier = 229, - locking_time = 231, - locking_time_modifier = 232, - mass = 4, - mass_modifier = 5, - massiveness = 582, - massiveness_modifier = 583, - mining_amount_crude_modifier = 234, - mining_amount_epriton_modifier = 235, - mining_amount_gammaterial_modifier = 649, - mining_amount_imentium_modifier = 236, - mining_amount_liquizit_modifier = 237, - mining_amount_modifier = 238, - mining_amount_silgium_modifier = 648, - mining_amount_stermonit_modifier = 240, - mining_amount_titan_modifier = 241, - mining_cycle_time_crude_modifier = 242, - mining_cycle_time_epriton_modifier = 243, - mining_cycle_time_gammaterial_modifier = 651, - mining_cycle_time_imentium_modifier = 244, - mining_cycle_time_liquizit_modifier = 245, - mining_cycle_time_modifier = 246, - mining_cycle_time_silgium_modifier = 650, - mining_cycle_time_stermonit_modifier = 247, - mining_cycle_time_titan_modifier = 248, - mining_probe_accuracy = 249, - mining_probe_accuracy_modifier = 250, - mining_probe_artifact_range = 615, - mining_probe_artifact_range_modifier = 616, - mining_probe_cycle_time_intrusion_modifier = 638, - mining_probe_intrusion_range = 633, - mining_probe_intrusion_range_modifier = 634, - mining_probe_range = 617, - mining_probe_tile_range_modifier = 252, - missile_ammo_cycle_time_modifier = 641, - missile_cycle_time_modifier = 253, - missile_miss = 341, - missile_miss_modifier = 342, - missile_range_modifier = 254, - mobile_teleport_cooldown = 579, - mobile_teleport_range = 577, - module_missile_range_modifier = 586, - optimal_range = 255, - optimal_range_modifier = 256, - pbs_turret_core_per_sec = 652, - plasma_cycle_time_modifier = 258, - plasma_falloff_modifier = 259, - plasma_optimal_range_modifier = 260, - powergrid_current = 261, - powergrid_max = 262, - powergrid_max_modifier = 263, - powergrid_usage = 265, - powergrid_usage_armor_modifier = 266, - powergrid_usage_engineering_modifier = 267, - powergrid_usage_ew_modifier = 268, - powergrid_usage_gathering_modifier = 269, - powergrid_usage_large_energytorpedo_modifier = 270, - powergrid_usage_large_industrial_aoe_modifier = 271, - powergrid_usage_large_ioncannon_modifier = 272, - powergrid_usage_large_laser_modifier = 273, - powergrid_usage_large_missile_modifier = 274, - powergrid_usage_large_plasma_modifier = 275, - powergrid_usage_large_projectile_modifier = 276, - powergrid_usage_large_railgun_modifier = 277, - powergrid_usage_medium_energytorpedo_modifier = 278, - powergrid_usage_medium_industrial_aoe_modifier = 279, - powergrid_usage_medium_ioncannon_modifier = 280, - powergrid_usage_medium_laser_modifier = 281, - powergrid_usage_medium_missile_modifier = 282, - powergrid_usage_medium_plasma_modifier = 283, - powergrid_usage_medium_projectile_modifier = 284, - powergrid_usage_medium_railgun_modifier = 285, - powergrid_usage_shield_generators_modifier = 287, - powergrid_usage_small_energytorpedo_modifier = 288, - powergrid_usage_small_industrial_aoe_modifier = 289, - powergrid_usage_small_ioncannon_modifier = 290, - powergrid_usage_small_laser_modifier = 291, - powergrid_usage_small_missile_modifier = 292, - powergrid_usage_small_plasma_modifier = 293, - powergrid_usage_small_projectile_modifier = 294, - powergrid_usage_small_railgun_modifier = 295, - powergrid_usage_weapons_modifier = 296, - projectile_ammo_cycle_time_modifier = 642, - projectile_cycle_time_modifier = 297, - projectile_falloff_modifier = 298, - projectile_optimal_range_modifier = 299, - railgun_ammo_cycle_time_modifier = 639, - railgun_cycle_time_modifier = 300, - railgun_falloff_modifier = 301, - railgun_optimal_range_modifier = 302, - reactor_radiation = 643, - reactor_radiation_modifier = 644, - received_repaired_modifier = 506, - resist_chemical = 303, - resist_chemical_modifier = 304, - resist_explosive = 306, - resist_explosive_modifier = 307, - resist_kinetic = 309, - resist_kinetic_modifier = 310, - resist_thermal = 312, - resist_thermal_modifier = 313, - sensor_strength = 315, - sensor_strength_eccm_modifier = 655, - sensor_strength_modifier = 316, - shield_absorbtion = 318, - shield_absorbtion_modifier = 319, - shield_cycle_time_modifier = 321, - shield_radius = 322, - signature_radius = 323, - signature_radius_modifier = 324, - slope = 326, - slope_modifier = 327, - speed_current = 576, - speed_max = 330, - speed_max_add = 504, - speed_max_modifier = 331, - stealth_strength = 603, - stealth_strength_modifier = 604, - terraforming_cycle_time_modifier = 333, - thermal_damage_to_core_modifier = 619, - tile_based_mining_probe_radius = 334, - turret_cycle_time_modifier = 335, - turret_fallof_modifier = 336, - turret_optimal_range_modifier = 337, - weapon_cycle_time_modifier = 540 - } + public enum AggregateField + { + undefined = 0, + accuracy = 1, + accuracy_modifier = 2, + aggrorange = 7, + aggrorange_modifier = 8, + ammo_reload_time = 9, + ammo_reload_time_modifier = 10, + aoe_explosion_radius = 12, + aoe_explosion_radius_modifier = 13, + armor_current = 575, + armor_max = 16, + armor_max_modifier = 17, + armor_repair_amount = 19, + armor_repair_amount_modifier = 20, + armor_repair_cycle_time_modifier = 22, + armor_repair_optimal_range_modifier = 23, + blob_effect = 595, + blob_emission = 587, + blob_emission_modifier = 590, + blob_emission_modifier_modifier = 598, + blob_emission_radius = 588, + blob_emission_radius_modifier = 591, + blob_level_high = 593, + blob_level_high_modifier = 594, + blob_level_low = 589, + blob_level_low_modifier = 592, + chemical_damage_to_core_modifier = 621, + construction_charge_amount = 647, + construction_charge_techmax = 666, + control_speed_boost_time_add = 505, + core_added = 24, + core_current = 574, + core_max = 26, + core_max_modifier = 27, + core_recharge_time = 29, + core_recharge_time_modifier = 30, + core_usage = 32, + core_usage_armor_hardener_modifier = 33, + core_usage_armor_repair_modifier = 34, + core_usage_cargo_scanner_modifier = 35, + core_usage_chassis_scanner_modifier = 36, + core_usage_cloaking_modifier = 37, + core_usage_detection_modul_modifier = 610, + core_usage_drillers_modifier = 38, + core_usage_energy_neutralizers_modifier = 39, + core_usage_energy_transfers_modifier = 40, + core_usage_energy_vampires_modifier = 41, + core_usage_energytorpedo_modifier = 42, + core_usage_gang_assist_module_modifier = 658, + core_usage_harvester_modifier = 43, + core_usage_industrial_aoe_modifier = 44, + core_usage_industrial_scanner_modifier = 45, + core_usage_ioncannon_modifier = 46, + core_usage_laser_modifier = 47, + core_usage_missile_modifier = 657, + core_usage_plasma_modifier = 48, + core_usage_projectile_modifier = 656, + core_usage_railgun_modifier = 49, + core_usage_remote_armor_repair_modifier = 50, + core_usage_remote_sensor_booster_modifier = 51, + core_usage_sensor_booster_modifier = 53, + core_usage_sensor_dampener_modifier = 54, + core_usage_sensor_jammer_modifier = 55, + core_usage_shield_generator_modifier = 56, + core_usage_shield_hardener_modifier = 57, + core_usage_stealth_modul_modifier = 612, + core_usage_terraformer_modifier = 58, + core_usage_weapon_modifier = 59, + core_usage_webber_modifier = 60, + cpu_current = 61, + cpu_max = 62, + cpu_max_modifier = 63, + cpu_usage = 64, + cpu_usage_armor_modifier = 65, + cpu_usage_detection_modul_modifier = 609, + cpu_usage_engineering_modifier = 66, + cpu_usage_ew_modifier = 67, + cpu_usage_gathering_modifier = 68, + cpu_usage_large_energy_neutralizer_modifier = 69, + cpu_usage_large_energy_transfer_modifier = 70, + cpu_usage_large_energy_vampire_modifier = 71, + cpu_usage_large_energytorpedo_modifier = 72, + cpu_usage_large_industrial_aoe_modifier = 73, + cpu_usage_large_ioncannon_modifier = 74, + cpu_usage_large_laser_modifier = 75, + cpu_usage_large_missile_modifier = 76, + cpu_usage_large_plasma_modifier = 77, + cpu_usage_large_projectile_modifier = 78, + cpu_usage_large_railgun_modifier = 79, + cpu_usage_medium_energy_neutralizer_modifier = 80, + cpu_usage_medium_energy_transfer_modifier = 81, + cpu_usage_medium_energy_vampire_modifier = 82, + cpu_usage_medium_energytorpedo_modifier = 83, + cpu_usage_medium_industrial_aoe_modifier = 84, + cpu_usage_medium_ioncannon_modifier = 85, + cpu_usage_medium_laser_modifier = 86, + cpu_usage_medium_missile_modifier = 87, + cpu_usage_medium_plasma_modifier = 88, + cpu_usage_medium_projectile_modifier = 89, + cpu_usage_medium_railgun_modifier = 90, + cpu_usage_mining_modifier = 91, + cpu_usage_remote_sensor_booster_modifier = 92, + cpu_usage_sensor_booster_modifier = 94, + cpu_usage_shield_generators_modifier = 581, + cpu_usage_small_energy_neutralizer_modifier = 95, + cpu_usage_small_energy_transfer_modifier = 96, + cpu_usage_small_energy_vampire_modifier = 97, + cpu_usage_small_energytorpedo_modifier = 98, + cpu_usage_small_industrial_aoe_modifier = 99, + cpu_usage_small_ioncannon_modifier = 100, + cpu_usage_small_laser_modifier = 101, + cpu_usage_small_missile_modifier = 102, + cpu_usage_small_plasma_modifier = 103, + cpu_usage_small_projectile_modifier = 104, + cpu_usage_small_railgun_modifier = 105, + cpu_usage_stealth_modul_modifier = 611, + cpu_usage_weapons_modifier = 106, + critical_damage_modifier = 645, + critical_damage_modifier_modifier = 646, + critical_hit_chance = 107, + critical_hit_chance_modifier = 108, + cycle_time = 110, + damage_chemical = 112, + damage_energytorpedo_modifier = 114, + damage_explosive = 115, + damage_industrial_aoe_modifier = 117, + damage_ioncannon_modifier = 122, + damage_kinetic = 123, + damage_large_energytorpedo_modifier = 125, + damage_large_industrial_aoe_modifier = 126, + damage_large_ioncannon_modifier = 127, + damage_large_laser_modifier = 128, + damage_large_missile_modifier = 129, + damage_large_plasma_modifier = 130, + damage_large_projectile_modifier = 131, + damage_large_railgun_modifier = 132, + damage_laser_modifier = 133, + damage_medium_energytorpedo_modifier = 134, + damage_medium_industrial_aoe_modifier = 135, + damage_medium_ioncannon_modifier = 136, + damage_medium_laser_modifier = 137, + damage_medium_missile_modifier = 138, + damage_medium_plasma_modifier = 139, + damage_medium_projectile_modifier = 140, + damage_medium_railgun_modifier = 141, + damage_missile_modifier = 142, + damage_modifier = 143, + damage_plasma_modifier = 145, + damage_projectile_modifier = 146, + damage_railgun_modifier = 147, + damage_small_energytorpedo_modifier = 148, + damage_small_industrial_aoe_modifier = 149, + damage_small_ioncannon_modifier = 150, + damage_small_laser_modifier = 151, + damage_small_missile_modifier = 152, + damage_small_plasma_modifier = 153, + damage_small_projectile_modifier = 154, + damage_small_railgun_modifier = 155, + damage_thermal = 156, + damage_toxic = 660, + damage_toxic_modifier = 661, + damage_turret_modifier = 158, + decay_chance = 613, + default_effect_range = 585, + despawn_time = 580, + detection_range = 599, + detection_range_modifier = 600, + detection_strength = 601, + detection_strength_modifier = 602, + devastate_resilience = 510, + devastate_resilience_modifier = 511, + devastating_chance = 507, + devastating_chance_modifier = 508, + ecm_strength = 162, + ecm_strength_modifier = 163, + effect_armor_max_modifier = 165, + effect_aura_radius = 536, + effect_aura_radius_modifier = 537, + effect_blob_emission_modifier = 596, + effect_blob_emission_radius_modifier = 597, + effect_core_recharge_time_modifier = 166, + effect_core_usage_gathering_modifier = 530, + effect_critical_hit_chance_modifier = 533, + effect_detection_strength_modifier = 605, + effect_devastate_resilience_modifier = 535, + effect_devastating_chance_modifier = 534, + effect_enhancer_aura_radius_modifier = 539, + effect_enhancer_detection_strength_modifier = 606, + effect_enhancer_lock_resist_modifier = 630, + effect_enhancer_massiveness_modifier = 632, + effect_enhancer_sensor_booster_locking_range_modifier = 167, + effect_enhancer_sensor_booster_locking_time_modifier = 168, + effect_enhancer_sensor_dampener_locking_range_modifier = 169, + effect_enhancer_sensor_dampener_locking_time_modifier = 170, + effect_enhancer_sensor_strength_modifier = 631, + effect_enhancer_speed_max_modifier = 171, + effect_enhancer_stealth_strength_modifier = 608, + effect_ew_optimal_range_modifier = 529, + effect_gathering_cycle_time_modifier = 544, + effect_harvesting_amount_modifier = 636, + effect_harvesting_cycle_time_modifier = 172, + effect_lock_resist = 629, + effect_locking_range_modifier = 173, + effect_locking_time_modifier = 531, + effect_massiveness = 625, + effect_massivness_speed_max_modifier = 623, + effect_mining_amount_modifier = 635, + effect_mining_cycle_time_modifier = 174, + effect_mining_probe_accuracy_modifier = 637, + effect_missile_range_modifier = 653, + effect_optimal_range_modifier = 175, + effect_repair_amount_modifier = 532, + effect_resist_chemical = 176, + effect_resist_explosive = 177, + effect_resist_kinetic = 178, + effect_resist_thermal = 179, + effect_sensor_booster_locking_range_modifier = 180, + effect_sensor_booster_locking_time_modifier = 181, + effect_sensor_dampener_locking_range_modifier = 182, + effect_sensor_dampener_locking_time_modifier = 183, + trigger_mass = 667, + mine_detection_range = 668, + mine_detection_range_modifier = 669, + effect_mine_detection_range_modifier = 670, + effect_enhancer_mine_detection_range_modifier = 671, + effect_sensor_strength_modifier = 626, + effect_shield_absorbtion_modifier = 184, + effect_signature_radius_modifier = 185, + effect_speed_highway_modifier = 622, + effect_speed_max_add = 503, + effect_speed_max_modifier = 186, + effect_stealth_strength_modifier = 607, + effect_weapon_cycle_time_modifier = 528, + energy_dispersion = 545, + energy_dispersion_modifier = 546, + energy_neutralized_amount = 187, + energy_neutralized_amount_modifier = 188, + energy_neutralizer_optimal_range_modifier = 190, + energy_transfer_amount = 191, + energy_transfer_amount_modifier = 192, + energy_transferer_optimal_range_modifier = 194, + energy_vampire_optimal_range_modifier = 195, + energy_vampired_amount = 196, + energy_vampired_amount_modifier = 197, + energytorpedo_cycle_time_modifier = 199, + energytorpedo_falloff_modifier = 200, + energytorpedo_optimal_range_modifier = 201, + engineering_cycle_time_modifier = 202, + engineering_optimal_range_modifier = 624, + ew_optimal_range_modifier = 203, + explosion_radius = 204, + explosion_radius_modifier = 205, + explosive_damage_to_core_modifier = 620, + falloff = 207, + falloff_modifier = 208, + gang_effect_enhancer_armor_max_modifier = 516, + gang_effect_enhancer_core_recharge_time_modifier = 523, + gang_effect_enhancer_core_usage_gathering_modifier = 517, + gang_effect_enhancer_critical_hit_chance_modifier = 522, + gang_effect_enhancer_devastating_chance_modifier = 526, + gang_effect_enhancer_ew_optimal_range_modifier = 515, + gang_effect_enhancer_gathering_cycle_time_modifier = 524, + gang_effect_enhancer_locking_range_modifier = 518, + gang_effect_enhancer_locking_time_modifier = 519, + gang_effect_enhancer_repair_amount_modifier = 521, + gang_effect_enhancer_resilience_modifier = 527, + gang_effect_enhancer_shield_absorbtion_ratio_modifier = 525, + gang_effect_enhancer_signature_radius_modifier = 520, + gang_effect_enhancer_speed_max_modifier = 513, + gang_effect_enhancer_weapon_cycle_time_modifier = 514, + gathering_cycle_time_modifier = 542, + harvesting_amount_electroplant_modifier = 614, + harvesting_amount_helioptris_modifier = 210, + harvesting_amount_modifier = 211, + harvesting_amount_prismocitae_modifier = 212, + harvesting_amount_triandlus_modifier = 214, + harvesting_cycle_time_modifier = 215, + industrial_aoe_cycle_time_modifier = 216, + industrial_aoe_optimal_range_modifier_modifier = 217, + ioncannon_cycle_time_modifier = 218, + ioncannon_falloff_modifier = 219, + ioncannon_optimal_range_modifier = 220, + kinetic_damage_to_core_modifier = 618, + laser_ammo_cycle_time_modifier = 640, + laser_cycle_time_modifier = 221, + laser_falloff_modifier = 222, + laser_optimal_range_modifier = 223, + least_optimal = 338, + least_optimal_modifier = 340, + lock_resist = 627, + lock_resist_modifier = 628, + locked_targets_current = 224, + locked_targets_max = 225, + locked_targets_max_bonus = 226, + locking_range = 228, + locking_range_modifier = 229, + locking_time = 231, + locking_time_modifier = 232, + mass = 4, + mass_modifier = 5, + massiveness = 582, + massiveness_modifier = 583, + mining_amount_crude_modifier = 234, + mining_amount_epriton_modifier = 235, + mining_amount_flux_modifier = 662, + mining_amount_gammaterial_modifier = 649, + mining_amount_imentium_modifier = 236, + mining_amount_liquizit_modifier = 237, + mining_amount_modifier = 238, + mining_amount_silgium_modifier = 648, + mining_amount_stermonit_modifier = 240, + mining_amount_titan_modifier = 241, + mining_cycle_time_crude_modifier = 242, + mining_cycle_time_epriton_modifier = 243, + mining_cycle_time_flux_modifier = 663, + mining_cycle_time_gammaterial_modifier = 651, + mining_cycle_time_imentium_modifier = 244, + mining_cycle_time_liquizit_modifier = 245, + mining_cycle_time_modifier = 246, + mining_cycle_time_silgium_modifier = 650, + mining_cycle_time_stermonit_modifier = 247, + mining_cycle_time_titan_modifier = 248, + mining_probe_accuracy = 249, + mining_probe_accuracy_modifier = 250, + mining_probe_artifact_range = 615, + mining_probe_artifact_range_modifier = 616, + mining_probe_cycle_time_intrusion_modifier = 638, + mining_probe_intrusion_range = 633, + mining_probe_intrusion_range_modifier = 634, + mining_probe_range = 617, + mining_probe_tile_range_modifier = 252, + missile_ammo_cycle_time_modifier = 641, + missile_cycle_time_modifier = 253, + missile_miss = 341, + missile_miss_modifier = 342, + missile_falloff_modifier = 658, + missile_range_modifier = 254, + mobile_teleport_cooldown = 579, + mobile_teleport_range = 577, + module_missile_falloff_modifier = 659, + module_missile_range_modifier = 586, + optimal_range = 255, + optimal_range_modifier = 256, + pbs_tech_limit = 664, //OPP: a no-op field for localization in gamma effect + pbs_turret_core_per_sec = 652, + plasma_cycle_time_modifier = 258, + plasma_falloff_modifier = 259, + plasma_optimal_range_modifier = 260, + powergrid_current = 261, + powergrid_max = 262, + powergrid_max_modifier = 263, + powergrid_usage = 265, + powergrid_usage_armor_modifier = 266, + powergrid_usage_engineering_modifier = 267, + powergrid_usage_ew_modifier = 268, + powergrid_usage_gathering_modifier = 269, + powergrid_usage_large_energytorpedo_modifier = 270, + powergrid_usage_large_industrial_aoe_modifier = 271, + powergrid_usage_large_ioncannon_modifier = 272, + powergrid_usage_large_laser_modifier = 273, + powergrid_usage_large_missile_modifier = 274, + powergrid_usage_large_plasma_modifier = 275, + powergrid_usage_large_projectile_modifier = 276, + powergrid_usage_large_railgun_modifier = 277, + powergrid_usage_medium_energytorpedo_modifier = 278, + powergrid_usage_medium_industrial_aoe_modifier = 279, + powergrid_usage_medium_ioncannon_modifier = 280, + powergrid_usage_medium_laser_modifier = 281, + powergrid_usage_medium_missile_modifier = 282, + powergrid_usage_medium_plasma_modifier = 283, + powergrid_usage_medium_projectile_modifier = 284, + powergrid_usage_medium_railgun_modifier = 285, + powergrid_usage_shield_generators_modifier = 287, + powergrid_usage_small_energytorpedo_modifier = 288, + powergrid_usage_small_industrial_aoe_modifier = 289, + powergrid_usage_small_ioncannon_modifier = 290, + powergrid_usage_small_laser_modifier = 291, + powergrid_usage_small_missile_modifier = 292, + powergrid_usage_small_plasma_modifier = 293, + powergrid_usage_small_projectile_modifier = 294, + powergrid_usage_small_railgun_modifier = 295, + powergrid_usage_weapons_modifier = 296, + projectile_ammo_cycle_time_modifier = 642, + projectile_cycle_time_modifier = 297, + projectile_falloff_modifier = 298, + projectile_optimal_range_modifier = 299, + pvp_no_teleport = 665, //OPP: field to use with zoneeffect to indicate prevention of teleport w/ pvp flag + railgun_ammo_cycle_time_modifier = 639, + railgun_cycle_time_modifier = 300, + railgun_falloff_modifier = 301, + railgun_optimal_range_modifier = 302, + reactor_radiation = 643, + reactor_radiation_modifier = 644, + received_repaired_modifier = 506, + resist_chemical = 303, + resist_chemical_modifier = 304, + resist_explosive = 306, + resist_explosive_modifier = 307, + resist_kinetic = 309, + resist_kinetic_modifier = 310, + resist_thermal = 312, + resist_thermal_modifier = 313, + sensor_strength = 315, + sensor_strength_eccm_modifier = 655, + sensor_strength_modifier = 316, + shield_absorbtion = 318, + shield_absorbtion_modifier = 319, + shield_cycle_time_modifier = 321, + shield_radius = 322, + signature_radius = 323, + signature_radius_modifier = 324, + slope = 326, + slope_modifier = 327, + speed_current = 576, + speed_max = 330, + speed_max_add = 504, + speed_max_modifier = 331, + stealth_strength = 603, + stealth_strength_modifier = 604, + terraforming_cycle_time_modifier = 333, + thermal_damage_to_core_modifier = 619, + tile_based_mining_probe_radius = 334, + turret_cycle_time_modifier = 335, + turret_fallof_modifier = 336, + turret_optimal_range_modifier = 337, + weapon_cycle_time_modifier = 540, + remote_control_bandwidth_max = 672, + remote_control_bandwidth_max_modifier = 673, + remote_control_bandwidth_usage = 674, + remote_control_operational_range = 675, + remote_control_operational_range_modifier = 676, + remote_control_lifetime = 677, + remote_control_lifetime_modifier = 678, + } } diff --git a/src/Perpetuum.ExportedTypes/AttributeFlags.cs b/src/Perpetuum.ExportedTypes/AttributeFlags.cs index 372d39803..b7076e89f 100644 --- a/src/Perpetuum.ExportedTypes/AttributeFlags.cs +++ b/src/Perpetuum.ExportedTypes/AttributeFlags.cs @@ -5,71 +5,71 @@ namespace Perpetuum.ExportedTypes { - public enum AttributeFlags : long - { - activeModule = 4, - alwaysStackable = 11, - ammo_required = 18, - consumable = 24, - delayed_modul = 25, - deployable = 23, - engage_combat = 7, - fittingCapacityModifier = 5, - forceOneCycle = 21, - gang_assist_module = 12, - gangEffect = 20, - instantActivate = 32, - invulnerable = 1, - item = 2, - large = 17, - mainbase = 30, - medium = 16, - nonattackable = 28, - nonlockable = 27, - nonRecyclable = 31, - nonrelocatable = 22, - nonStackable = 10, - NOT_USED = 63, - offensive_module = 6, - onePerRobot = 3, - passiveModule = 19, - player = 0, - primary_locked_target = 13, - pvp_support = 26, - self_effect = 8, - self_target = 14, - small = 15, - targetIsRobot = 9, - undefined_33 = 33, - undefined_34 = 34, - undefined_35 = 35, - undefined_36 = 36, - undefined_37 = 37, - undefined_38 = 38, - undefined_39 = 39, - undefined_40 = 40, - undefined_41 = 41, - undefined_42 = 42, - undefined_43 = 43, - undefined_44 = 44, - undefined_45 = 45, - undefined_46 = 46, - undefined_47 = 47, - undefined_48 = 48, - undefined_49 = 49, - undefined_50 = 50, - undefined_51 = 51, - undefined_52 = 52, - undefined_53 = 53, - undefined_54 = 54, - undefined_55 = 55, - undefined_56 = 56, - undefined_57 = 57, - undefined_58 = 58, - undefined_59 = 59, - undefined_60 = 60, - undefined_61 = 61, - undefined_62 = 62, - usable = 29 - } + public enum AttributeFlags : long + { + player = 0, + invulnerable = 1, + item = 2, + onePerRobot = 3, + activeModule = 4, + fittingCapacityModifier = 5, + offensive_module = 6, + engage_combat = 7, + self_effect = 8, + targetIsRobot = 9, + nonStackable = 10, + alwaysStackable = 11, + gang_assist_module = 12, + primary_locked_target = 13, + self_target = 14, + small = 15, + medium = 16, + large = 17, + ammo_required = 18, + passiveModule = 19, + gangEffect = 20, + forceOneCycle = 21, + nonrelocatable = 22, + deployable = 23, + consumable = 24, + delayed_modul = 25, + pvp_support = 26, + nonlockable = 27, + nonattackable = 28, + usable = 29, + mainbase = 30, + nonRecyclable = 31, + instantActivate = 32, + undefined_33 = 33, + undefined_34 = 34, + undefined_35 = 35, + undefined_36 = 36, + undefined_37 = 37, + undefined_38 = 38, + undefined_39 = 39, + undefined_40 = 40, + undefined_41 = 41, + undefined_42 = 42, + undefined_43 = 43, + undefined_44 = 44, + undefined_45 = 45, + undefined_46 = 46, + undefined_47 = 47, + undefined_48 = 48, + undefined_49 = 49, + undefined_50 = 50, + undefined_51 = 51, + undefined_52 = 52, + undefined_53 = 53, + undefined_54 = 54, + undefined_55 = 55, + undefined_56 = 56, + undefined_57 = 57, + undefined_58 = 58, + undefined_59 = 59, + undefined_60 = 60, + undefined_61 = 61, + undefined_62 = 62, + NOT_USED = 63, + } } diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index a7be71856..3e5954442 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -1,623 +1,724 @@ // 53AAD63B // This code was generated by a tool. // date: 03/05/2017 11:20:34 +// Updated manually by OPP 2021/03/03 namespace Perpetuum.ExportedTypes { - [System.Flags] - public enum CategoryFlags : long - { - undefined = 0, - cf_active_hacking_sap = 0x0000000000030592, - cf_admin_items = 0x0000000000000196, - cf_aggrorange_decreasers = 0x000000000005050f, - cf_alarm_switch = 0x0000000001040696, - cf_alliance = 0x0000000000000282, - cf_ammo = 0x000000000000000a, - cf_ammo_calibration_programs = 0x0000000000030416, - cf_ammo_corebooster_calibration_programs = 0x000000000b030416, - cf_ammo_energy_torpedo_calibration_programs = 0x0000000009030416, - cf_ammo_harvesting_calibration_programs = 0x0000000007030416, - cf_ammo_industrial_aoe_calibration_programs = 0x000000000d030416, - cf_ammo_ioncannon_calibration_programs = 0x000000000a030416, - cf_ammo_laser_calibration_programs = 0x0000000002030416, - cf_ammo_mining_calibration_programs = 0x000000000c030416, - cf_ammo_mining_probe_calibration_programs = 0x000000000e030416, - cf_ammo_missile_calibration_programs = 0x0000000004030416, - cf_ammo_plasma_calibration_programs = 0x0000000008030416, - cf_ammo_projectile_calibration_programs = 0x0000000005030416, - cf_ammo_railgun_calibration_programs = 0x0000000001030416, - cf_ammo_terraforming_multi = 0x000000000001100a, - cf_aoe_calibration_programs = 0x0000000008020416, - cf_aoe_projectile = 0x000000000502070f, - cf_aoe_projectile_tile = 0x000000010502070f, - cf_aoe_projectile_unit = 0x000000020502070f, - cf_aoe_unit_ammo = 0x0000000000000e0a, - cf_area_based = 0x0000000000020390, - cf_area_bomb = 0x0000000000020392, - cf_area_bomb_deployer = 0x0000000000020498, - cf_arena_guard = 0x0000000000000692, - cf_arena_teleport_deployer = 0x0000000000000698, - cf_armor_calibration_programs = 0x0000000001040416, - cf_armor_equipment = 0x000000000000010f, - cf_armor_hardeners = 0x000000000004010f, - cf_armor_plates = 0x000000000001010f, - cf_armor_repair_systems = 0x000000000002010f, - cf_armor_repair_upgrades = 0x000000000006010f, - cf_artifact_data = 0x0000000000000914, - cf_base = 0x0000000000000178, - cf_base_item_shop = 0x000000000002016e, - cf_basic_commodities = 0x0000000000000214, - cf_battle_stance_moduls = 0x000000000108040f, - cf_blob_emission_modulator = 0x000000000007050f, - cf_blob_emission_modulator_ammo = 0x0000000000000f0a, - cf_blob_emitter = 0x0000000000000292, - cf_buoy_charger_ammo = 0x000000000002100a, - cf_calibration_program_forge = 0x00000000000c036e, - cf_calibration_programs = 0x0000000000000416, - cf_cargo_scanner = 0x000000000003040f, - cf_chassis_calibration_programs = 0x0000000002050416, - cf_chassis_scanner = 0x000000000004040f, - cf_chemical_armor_hardeners = 0x000000000104010f, - cf_common_energy_cores = 0x0000000000010514, - cf_common_robotshards = 0x0000000000010414, - cf_component_items = 0x0000000000020595, - cf_construction_ammo = 0x000000000001110a, - cf_construction_ammo_main = 0x000000000000110a, - cf_construction_modules = 0x000000000009060f, - cf_consumable_boosters = 0x0000000000010996, - cf_consumable_items = 0x0000000000000996, - cf_container = 0x0000000000000015, - cf_container_capsule = 0x0000000000000298, - cf_core_batteries = 0x000000000008030f, - cf_core_booster_ammo = 0x000000000000070a, - cf_core_boosters = 0x000000000009030f, - cf_core_rechargers = 0x000000000004030f, - cf_corporate_hangar = 0x0000000000060815, - cf_corporate_hangar_folder = 0x0000000000070815, - cf_corporation = 0x0000000000000182, - cf_courier_items = 0x0000000000000195, - cf_cpu_upgrades = 0x000000000006040f, - cf_crawler_calibration_programs = 0x0000000002010416, - cf_crawler_chassis = 0x0000000000020250, - cf_crawler_head = 0x0000000000020150, - cf_crawler_leg = 0x0000000000020350, - cf_crawlers = 0x0000000000000301, - cf_decor = 0x000000000000005a, - cf_decor_buildings = 0x000000000000055a, - cf_decor_general = 0x000000000001055a, - cf_decor_general_element = 0x000000000101055a, - cf_decor_nuimqol = 0x000000000003055a, - cf_decor_nuimqol_element = 0x000000000303055a, - cf_decor_nuimqol_general = 0x000000000103055a, - cf_decor_nuimqol_misc = 0x000000000000025a, - cf_decor_nuimqol_special = 0x000000000203055a, - cf_decor_pelistal = 0x000000000002055a, - cf_decor_pelistal_element = 0x000000000302055a, - cf_decor_pelistal_general = 0x000000000102055a, - cf_decor_pelistal_misc = 0x000000000000045a, - cf_decor_pelistal_special = 0x000000000202055a, - cf_decor_template = 0x000000000000065a, - cf_decor_thelodica = 0x000000000004055a, - cf_decor_thelodica_element = 0x000000000304055a, - cf_decor_thelodica_general = 0x000000000104055a, - cf_decor_thelodica_misc = 0x000000000000035a, - cf_decor_thelodica_special = 0x000000000204055a, - cf_default_alliance = 0x0000000000020282, - cf_default_corporation = 0x0000000000010182, - cf_deployable_structure = 0x0000000000000998, - cf_destrucion_sap = 0x0000000000010592, - cf_detection_modules = 0x000000000009040f, - cf_documents = 0x0000000000000016, - cf_dogtags = 0x0000000000000091, - cf_drillers = 0x000000000001060f, - cf_dynamic_cprg = 0x0000000000000099, - cf_dynamic_teleports = 0x0000000000060278, - cf_eccm = 0x000000000004050f, - cf_ecm_boosters = 0x000000000008050f, - cf_egg_deployers = 0x0000000000000498, - cf_eggs = 0x0000000000000392, - cf_electronic_warfare_equipment = 0x000000000000050f, - cf_electronics_equipment = 0x000000000000040f, - cf_energy_cores = 0x0000000000000514, - cf_energy_neutralizers = 0x000000000003030f, - cf_energy_transferers = 0x000000000001030f, - cf_energy_vampires = 0x000000000002030f, - cf_energy_warfare_upgrades = 0x00000000000d030f, - cf_energytorpedo = 0x000000000009070f, - cf_energytorpedo_ammo = 0x0000000000000c0a, - cf_energytorpedo_calibration_programs = 0x0000000007020416, - cf_engineering_calibration_programs = 0x0000000003040416, - cf_engineering_equipment = 0x000000000000030f, - cf_ew_calibration_programs = 0x0000000002040416, - cf_ew_resist = 0x0000000000010a0f, - cf_explosive_armor_hardeners = 0x000000000304010f, - cf_explosive_kers = 0x00000000020b030f, - cf_field_accessories = 0x0000000000000098, - cf_field_containers = 0x0000000000020a15, - cf_field_terminal = 0x0000000000020178, - cf_gamma_commodities = 0x0000000000060214, - cf_gang_assist_calibration_programs = 0x0000000005040416, - cf_gang_assist_coordinated_manuevering = 0x000000000008090f, - cf_gang_assist_core_management = 0x00000000000c090f, - cf_gang_assist_defense = 0x000000000004090f, - cf_gang_assist_devastating = 0x00000000000b090f, - cf_gang_assist_ewar = 0x000000000003090f, - cf_gang_assist_fast_extracting = 0x00000000000d090f, - cf_gang_assist_industry = 0x000000000005090f, - cf_gang_assist_information = 0x000000000006090f, - cf_gang_assist_maintance = 0x000000000009090f, - cf_gang_assist_modules = 0x000000000000090f, - cf_gang_assist_precision_firing = 0x00000000000a090f, - cf_gang_assist_resilience = 0x00000000000f090f, - cf_gang_assist_shared_dataprocessing = 0x000000000007090f, - cf_gang_assist_shield_calculations = 0x00000000000e090f, - cf_gang_assist_siege = 0x000000000002090f, - cf_gang_assist_speed = 0x000000000001090f, - cf_gate = 0x0000000000000a92, - cf_gathering_equipment = 0x000000000000060f, - cf_generic_random_items = 0x0000000000010595, - cf_geoscan_documents = 0x0000000000000695, - cf_geothermal = 0x0000000000050114, - cf_gift_packages = 0x0000000000000896, - cf_gm_test = 0x0000000000000701, - cf_groups = 0x0000000000000082, - cf_harvesters = 0x000000000006060f, - cf_harvesting_ammo = 0x0000000000000a0a, - cf_head_calibration_programs = 0x0000000001050416, - cf_heavy_mechs = 0x0000000000000501, - cf_heavymech_calibration_programs = 0x0000000004010416, - cf_heavymech_chassis = 0x0000000000040250, - cf_heavymech_head = 0x0000000000040150, - cf_heavymech_leg = 0x0000000000040350, - cf_hightier_commodities = 0x0000000000050214, - cf_hybrid_robot = 0x0000000000000901, - cf_ice = 0x000000000000029b, - cf_industrial_aoe = 0x00000000000a070f, - cf_industrial_aoe_calibration_programs = 0x000000000a020416, - cf_industrial_aoe_tile_ammo = 0x0000000000000d0a, - cf_industrial_crawlers = 0x0000000000020e01, - cf_industrial_egg_deployer = 0x0000000000040598, - cf_industrial_heavy_gliders = 0x0000000000050e01, - cf_industrial_heavymechs = 0x0000000000040e01, - cf_industrial_mechs = 0x0000000000030e01, - cf_industrial_robots = 0x0000000000000e01, - cf_industrial_runners = 0x0000000000010e01, - cf_industrial_scanner = 0x000000000005040f, - cf_industry_calibration_programs = 0x0000000004040416, - cf_indy_dogtags = 0x0000000000000591, - cf_infinite_capacity_box = 0x0000000000030815, - cf_infinite_capacity_containers = 0x0000000000000815, - cf_inland_mobile_teleport_capsule = 0x0000000000010198, - cf_insurance_facility = 0x00000000000a036e, - cf_intel_documents = 0x0000000000000795, - cf_intrusion_objects = 0x0000000000000592, - cf_intrusion_pbs_deployers = 0x0000000000040498, - cf_intrusion_pbs_eggs = 0x0000000000040392, - cf_intrusion_turrets = 0x0000000000050592, - cf_ioncannon = 0x000000000008070f, - cf_ioncannon_ammo = 0x0000000000000b0a, - cf_ioncannon_calibration_programs = 0x0000000006020416, - cf_item_speed_boosters = 0x0000000000000396, - cf_item_supply = 0x0000000000050696, - cf_items = 0x0000000000000096, - cf_kernels = 0x0000000000000494, - cf_kers = 0x00000000000b030f, - cf_kinetic_armor_hardeners = 0x000000000204010f, - cf_kinetic_kers = 0x00000000010b030f, - cf_kiosk = 0x0000000000010696, - cf_large_aoe_tile = 0x000003010502070f, - cf_large_aoe_unit = 0x000003020502070f, - cf_large_aoe_unit_ammo = 0x0000000000030e0a, - cf_large_armor_plates = 0x000000000301010f, - cf_large_armor_repairers = 0x000000000302010f, - cf_large_core_batteries = 0x000000000308030f, - cf_large_core_boosters = 0x000000000309030f, - cf_large_crystals = 0x000000000003020a, - cf_large_energy_neutralizers = 0x000000000303030f, - cf_large_energy_transferers = 0x000000000301030f, - cf_large_energy_vampires = 0x000000000302030f, - cf_large_energytorpedo = 0x000000000309070f, - cf_large_energytorpedo_ammo = 0x0000000000030c0a, - cf_large_industrial_aoe = 0x00000000030a070f, - cf_large_industrial_aoe_tile_ammo = 0x0000000000030d0a, - cf_large_ioncannon = 0x000000000308070f, - cf_large_ioncannon_ammo = 0x0000000000030b0a, - cf_large_lasers = 0x000000000301070f, - cf_large_missile_ammo = 0x000000000003040a, - cf_large_missile_launchers = 0x000000000303070f, - cf_large_plasma = 0x000000000407070f, - cf_large_plasma_ammo = 0x000000000003060a, - cf_large_projectile_ammo = 0x000000000003030a, - cf_large_railgun_ammo = 0x000000000003010a, - cf_large_railguns = 0x000000000304070f, - cf_large_remote_armor_repairers = 0x000000000303010f, - cf_large_shield_generators = 0x000000000301020f, - cf_large_single_projectile = 0x000000030402070f, - cf_laser_ammo = 0x000000000000020a, - cf_laser_calibration_programs = 0x0000000002020416, - cf_laser_upgrades = 0x000000000305070f, - cf_lasers = 0x000000000001070f, - cf_leg_calibration_programs = 0x0000000003050416, - cf_lightweight_frame = 0x000000000005010f, - cf_limited_capacity_box = 0x0000000000040915, - cf_limited_capacity_containers = 0x0000000000000915, - cf_liquid = 0x0000000000040114, - cf_lock_breaker = 0x0000000000020a0f, - cf_logical_storage = 0x0000000000000017, - cf_longrange_webber = 0x000000000201050f, - cf_loot = 0x0000000000020815, - cf_loot_container_object = 0x0000000000000192, - cf_lootcontainers = 0x0000000000000a15, - cf_lootonly_containers = 0x0000000000010a15, - cf_lottery_items = 0x000000000000049b, - cf_maneuvering_equipment = 0x00000000000a030f, - cf_markets = 0x000000000000016e, - cf_massmodifiers = 0x000000000007030f, - cf_material = 0x0000000000000014, - cf_material_scan_result = 0x0000000000000390, - cf_mech_calibration_programs = 0x0000000003010416, - cf_mech_chassis = 0x0000000000030250, - cf_mech_head = 0x0000000000030150, - cf_mech_leg = 0x0000000000030350, - cf_mechs = 0x0000000000000401, - cf_medium_aoe_tile = 0x000002010502070f, - cf_medium_aoe_unit = 0x000002020502070f, - cf_medium_aoe_unit_ammo = 0x0000000000020e0a, - cf_medium_armor_plates = 0x000000000201010f, - cf_medium_armor_repairers = 0x000000000202010f, - cf_medium_core_batteries = 0x000000000208030f, - cf_medium_core_boosters = 0x000000000209030f, - cf_medium_crystals = 0x000000000002020a, - cf_medium_drillers = 0x000000000201060f, - cf_medium_drillers_old = 0x000000000002060f, - cf_medium_energy_neutralizers = 0x000000000203030f, - cf_medium_energy_transferers = 0x000000000201030f, - cf_medium_energy_vampires = 0x000000000202030f, - cf_medium_energytorpedo = 0x000000000209070f, - cf_medium_energytorpedo_ammo = 0x0000000000020c0a, - cf_medium_harvesters = 0x000000000206060f, - cf_medium_harvesters_old = 0x000000000007060f, - cf_medium_industrial_aoe = 0x00000000020a070f, - cf_medium_industrial_aoe_tile_ammo = 0x0000000000020d0a, - cf_medium_ioncannon = 0x000000000208070f, - cf_medium_ioncannon_ammo = 0x0000000000020b0a, - cf_medium_lasers = 0x000000000201070f, - cf_medium_missile_ammo = 0x000000000002040a, - cf_medium_missile_launchers = 0x000000000203070f, - cf_medium_plasma = 0x000000000307070f, - cf_medium_plasma_ammo = 0x000000000002060a, - cf_medium_projectile_ammo = 0x000000000002030a, - cf_medium_railgun_ammo = 0x000000000002010a, - cf_medium_railguns = 0x000000000204070f, - cf_medium_remote_armor_repairers = 0x000000000203010f, - cf_medium_shield_generators = 0x000000000201020f, - cf_medium_single_projectile = 0x000000020402070f, - cf_mill = 0x000000000008036e, - cf_mining_ammo = 0x000000000000050a, - cf_mining_probe_ammo = 0x000000000000090a, - cf_mining_probe_ammo_area = 0x000000000001090a, - cf_mining_probe_ammo_artifact = 0x000000000004090a, - cf_mining_probe_ammo_direction = 0x000000000006090a, - cf_mining_probe_ammo_intrusion = 0x000000000005090a, - cf_mining_probe_ammo_one_tile = 0x000000000003090a, - cf_mining_probe_ammo_tile = 0x000000000002090a, - cf_mining_probe_result = 0x0000000000000316, - cf_mining_probe_result_area = 0x0000000000010316, - cf_mining_probe_result_tile = 0x0000000000020316, - cf_mining_probes = 0x000000000005060f, - cf_mining_upgrades = 0x000000000004060f, - cf_missile_ammo = 0x000000000000040a, - cf_missile_calibration_programs = 0x0000000004020416, - cf_missile_upgrades = 0x000000000405070f, - cf_missiles = 0x000000000003070f, - cf_mission_agent = 0x000000000000046e, - cf_mission_calibration_programs = 0x0000000000060416, - cf_mission_coin = 0x000000000000039b, - cf_mission_egg_deployer = 0x0000000000050598, - cf_mission_items = 0x0000000000000095, - cf_mission_mineral = 0x0000000000000495, - cf_mission_structures = 0x0000000000000696, - cf_mission_switch = 0x0000000000040696, - cf_mobile_arena_teleport = 0x0000000003060278, - cf_mobile_teleport = 0x0000000001060278, - cf_mobile_teleport_capsule = 0x0000000000000198, - cf_mobile_world_teleport = 0x0000000002060278, - cf_module_calibration_programs = 0x0000000000040416, - cf_neuralyzer = 0x00000000000c030f, - cf_noobot = 0x0000000000000101, - cf_npc = 0x000000000000008f, - cf_npc_egg_deployer = 0x0000000000000598, - cf_npc_eggs = 0x0000000000010392, - cf_npc_indy = 0x000000000000068f, - cf_npc_nuimqol = 0x000000000000048f, - cf_npc_pelistal = 0x000000000000058f, - cf_npc_police = 0x000000000000018f, - cf_npc_test = 0x000000000000028f, - cf_npc_thelodica = 0x000000000000038f, - cf_nuimqol_commodities = 0x0000000000040214, - cf_nuimqol_crawlers = 0x0000000000020d01, - cf_nuimqol_dogtags = 0x0000000000000391, - cf_nuimqol_egg_deployer = 0x0000000000010598, - cf_nuimqol_energy_cores = 0x0000000000040514, - cf_nuimqol_heavymechs = 0x0000000000040d01, - cf_nuimqol_mechs = 0x0000000000030d01, - cf_nuimqol_robots = 0x0000000000000d01, - cf_nuimqol_robotshards = 0x0000000000030414, - cf_nuimqol_runners = 0x0000000000010d01, - cf_ore = 0x0000000000020114, - cf_organic = 0x0000000000010114, - cf_others = 0x0000000000000296, - cf_outpost = 0x0000000001010178, - cf_outpost_mill = 0x000000000108036e, - cf_outpost_prototyper = 0x000000000109036e, - cf_outpost_refinery = 0x000000000101036e, - cf_outpost_repair = 0x000000000105036e, - cf_outpost_reprocessor = 0x000000000104036e, - cf_outpost_research_lab = 0x000000000106036e, - cf_overcharged_armor_resist = 0x0000000000030a0f, - cf_package_activator = 0x000000000000019b, - cf_package_activator_credit = 0x000000000003019b, - cf_package_activator_ep = 0x000000000001019b, - cf_package_activator_spark = 0x000000000002019b, - cf_passive_hacking_sap = 0x0000000000040592, - cf_pbs = 0x0000000000030492, - cf_pbs_armor_repairer = 0x0000000007030492, - cf_pbs_aura_emitter_capsules = 0x0000000000030e9a, - cf_pbs_basic_transmitter_capsules = 0x000000000001039a, - cf_pbs_battery_capsules = 0x000000000003039a, - cf_pbs_calibration_forge_nodes = 0x0000000803030492, - cf_pbs_calibration_programs = 0x0000000000070416, - cf_pbs_capsules = 0x000000000000009a, - cf_pbs_control_tower = 0x0000000008030492, - cf_pbs_control_tower_capsules = 0x000000000000119a, - cf_pbs_core_transmitter = 0x0000000002030492, - cf_pbs_core_transmitter_capsules = 0x000000000000039a, - cf_pbs_docking_base = 0x0000000009030492, - cf_pbs_docking_base_capsules = 0x000000000000019a, - cf_pbs_effect_emitter = 0x0000000005030492, - cf_pbs_effect_emitter_capsules = 0x0000000000000e9a, - cf_pbs_effect_supplier = 0x0000000004030492, - cf_pbs_effect_supplier_capsules = 0x0000000000000d9a, - cf_pbs_egg = 0x0000000000050392, - cf_pbs_energy_well = 0x000000000a030492, - cf_pbs_energy_well_capsules = 0x000000000000129a, - cf_pbs_ew_turret_capsules = 0x000000000004109a, - cf_pbs_highway_node = 0x000000000b030492, - cf_pbs_highway_node_capsule = 0x000000000000139a, - cf_pbs_laser_turret_capsules = 0x000000000002109a, - cf_pbs_masker_tower_capsules = 0x0000000000020e9a, - cf_pbs_mill = 0x000000000208036e, - cf_pbs_mill_nodes = 0x0000000503030492, - cf_pbs_miner_tower_capsules = 0x0000000000010e9a, - cf_pbs_mining_towers = 0x000000000c030492, - cf_pbs_missile_turret_capsules = 0x000000000003109a, - cf_pbs_production_capsules = 0x000000000000049a, - cf_pbs_production_ct_merge_capsules = 0x000000000007049a, - cf_pbs_production_decoder_complex_capsules = 0x000000000008049a, - cf_pbs_production_mill_capsules = 0x000000000006049a, - cf_pbs_production_nodes = 0x0000000003030492, - cf_pbs_production_prototyper_capsules = 0x000000000004049a, - cf_pbs_production_refinery_capsules = 0x000000000001049a, - cf_pbs_production_repair_capsules = 0x000000000003049a, - cf_pbs_production_reprocessor_capsules = 0x000000000002049a, - cf_pbs_production_research_lab_capsules = 0x000000000005049a, - cf_pbs_production_upgrade_capsules = 0x0000000000000c9a, - cf_pbs_production_upgrade_nodes = 0x0000000303030492, - cf_pbs_prototyper = 0x000000000209036e, - cf_pbs_prototyper_nodes = 0x0000000403030492, - cf_pbs_railgun_turret_capsules = 0x000000000001109a, - cf_pbs_reactor = 0x0000000001030492, - cf_pbs_reactor_booster = 0x0000000000000394, - cf_pbs_reactor_capsules = 0x000000000000029a, - cf_pbs_refinery = 0x000000000201036e, - cf_pbs_refinery_nodes = 0x0000000603030492, - cf_pbs_repair = 0x000000000205036e, - cf_pbs_repair_nodes = 0x0000000203030492, - cf_pbs_repairer_capsules = 0x0000000000000f9a, - cf_pbs_reprocessor = 0x000000000204036e, - cf_pbs_reprocessor_nodes = 0x0000000703030492, - cf_pbs_reseach_lab = 0x000000000206036e, - cf_pbs_reseach_lab_nodes = 0x0000000103030492, - cf_pbs_research_kit_forge_nodes = 0x0000000903030492, - cf_pbs_turret = 0x0000000006030492, - cf_pbs_turret_capsules = 0x000000000000109a, - cf_pbs_XL_transmitter_capsules = 0x000000000002039a, - cf_pelistal_commodities = 0x0000000000020214, - cf_pelistal_crawlers = 0x0000000000020b01, - cf_pelistal_dogtags = 0x0000000000000191, - cf_pelistal_egg_deployer = 0x0000000000020598, - cf_pelistal_energy_cores = 0x0000000000030514, - cf_pelistal_heavymechs = 0x0000000000040b01, - cf_pelistal_mechs = 0x0000000000030b01, - cf_pelistal_robots = 0x0000000000000b01, - cf_pelistal_robotshards = 0x0000000000040414, - cf_pelistal_runners = 0x0000000000010b01, - cf_plant_seed = 0x0000000000000398, - cf_plasma = 0x000000000007070f, - cf_plasma_ammo = 0x000000000000060a, - cf_plasma_bomb = 0x0000000000000898, - cf_plasmagun_calibration_programs = 0x0000000003020416, - cf_player = 0x000000000000008c, - cf_player_built = 0x0000000000000492, - cf_police = 0x000000000000008e, - cf_police_turret = 0x0000000000010801, - cf_portals = 0x0000000000000c92, - cf_powergrid_upgrades = 0x000000000005030f, - cf_primary_commodities = 0x0000000000010214, - cf_private_alliance = 0x0000000000010282, - cf_private_container = 0x0000000000010915, - cf_private_corporation = 0x0000000000020182, - cf_production_facilities = 0x000000000000036e, - cf_production_items = 0x0000000000000094, - cf_projectile_ammo = 0x000000000000030a, - cf_projectile_calibration_programs = 0x0000000005020416, - cf_projectile_upgrades = 0x000000000205070f, - cf_projectiles = 0x000000000002070f, - cf_prototyper = 0x000000000009036e, - cf_proximity_probe_deployer = 0x0000000000000798, - cf_proximity_probes = 0x0000000000010492, - cf_public_container = 0x0000000000040815, - cf_public_corporation_hangar_storage = 0x0000000000050815, - cf_public_docking_base = 0x0000000000010178, - cf_public_market = 0x000000000001016e, - cf_public_teleport_column = 0x0000000000010278, - cf_public_vendor = 0x000000000001026e, - cf_railgun_ammo = 0x000000000000010a, - cf_railgun_calibration_programs = 0x0000000001020416, - cf_railgun_upgrades = 0x000000000105070f, - cf_railguns = 0x000000000004070f, - cf_random_artifacts = 0x0000000000030595, - cf_random_calibration_programs = 0x0000000000080416, - cf_random_items = 0x0000000000000595, - cf_random_research_kits = 0x0000000000000594, - cf_rare_material = 0x0000000000000714, - cf_raw_material = 0x0000000000000114, - cf_reactor_cores = 0x0000000000000294, - cf_reactor_plasma = 0x0000000000000614, - cf_reactor_sealings = 0x00000000000e030f, - cf_redeemables = 0x000000000000009b, - cf_refinery_facility = 0x000000000001036e, - cf_remote_armor_repairers = 0x000000000003010f, - cf_remote_sensor_boosters = 0x000000000002040f, - cf_repair_facility = 0x000000000005036e, - cf_reprocessor_facility = 0x000000000004036e, - cf_research_kit_forge = 0x00000000000b036e, - cf_research_kits = 0x0000000000000194, - cf_research_lab = 0x000000000006036e, - cf_resistance_plating = 0x000000000504010f, - cf_rifts = 0x0000000000000b92, - cf_robot_assembler = 0x000000000007036e, - cf_robot_calibration_programs = 0x0000000000010416, - cf_robot_chassis = 0x0000000000000250, - cf_robot_component_calibration_programs = 0x0000000000050416, - cf_robot_components = 0x0000000000000050, - cf_robot_equipment = 0x000000000000000f, - cf_robot_head = 0x0000000000000150, - cf_robot_inventory = 0x0000000000030915, - cf_robot_leg = 0x0000000000000350, - cf_robots = 0x0000000000000001, - cf_robotshards = 0x0000000000000414, - cf_rock = 0x000000000000015a, - cf_runner_calibration_programs = 0x0000000001010416, - cf_runner_chassis = 0x0000000000010250, - cf_runner_head = 0x0000000000010150, - cf_runner_leg = 0x0000000000010350, - cf_runners = 0x0000000000000201, - cf_scan_result = 0x0000000000000090, - cf_sensor_boosters = 0x000000000001040f, - cf_sensor_dampeners = 0x000000000003050f, - cf_sensor_jammers = 0x000000000002050f, - cf_shield_equipment = 0x000000000000020f, - cf_shield_generators = 0x000000000001020f, - cf_shield_hardener = 0x000000000002020f, - cf_shortrange_webber = 0x000000000101050f, - cf_siege_hack_modules = 0x000000000006050f, - cf_simple_switch = 0x0000000002040696, - cf_single_projectile = 0x000000000402070f, - cf_small_aoe_projectile_tile = 0x000001010502070f, - cf_small_aoe_unit = 0x000001020502070f, - cf_small_aoe_unit_ammo = 0x0000000000010e0a, - cf_small_armor_plates = 0x000000000101010f, - cf_small_armor_repairers = 0x000000000102010f, - cf_small_core_batteries = 0x000000000108030f, - cf_small_core_boosters = 0x000000000109030f, - cf_small_crystals = 0x000000000001020a, - cf_small_drillers = 0x000000000101060f, - cf_small_energy_neutralizers = 0x000000000103030f, - cf_small_energy_transferers = 0x000000000101030f, - cf_small_energy_vampires = 0x000000000102030f, - cf_small_energytorpedo = 0x000000000109070f, - cf_small_energytorpedo_ammo = 0x0000000000010c0a, - cf_small_harvesters = 0x000000000106060f, - cf_small_industrial_aoe = 0x00000000010a070f, - cf_small_industrial_aoe_tile_ammo = 0x0000000000010d0a, - cf_small_ioncannon = 0x000000000108070f, - cf_small_ioncannon_ammo = 0x0000000000010b0a, - cf_small_lasers = 0x000000000101070f, - cf_small_missile_ammo = 0x000000000001040a, - cf_small_missile_launchers = 0x000000000103070f, - cf_small_plasma = 0x000000000107070f, - cf_small_plasma_ammo = 0x000000000001060a, - cf_small_projectile_ammo = 0x000000000001030a, - cf_small_railgun_ammo = 0x000000000001010a, - cf_small_railguns = 0x000000000104070f, - cf_small_remote_armor_repairers = 0x000000000103010f, - cf_small_shield_generator = 0x000000000101020f, - cf_small_single_projectile = 0x000000010402070f, - cf_small_terraformer = 0x000000000001080f, - cf_soil = 0x0000000000000314, - cf_specimen_processing_sap = 0x0000000000020592, - cf_stance_moduls = 0x000000000008040f, - cf_starter_item = 0x0000000000000395, - cf_station_services = 0x000000000000006e, - cf_stealth_modules = 0x00000000000a040f, - cf_structures = 0x0000000000000078, - cf_supressor_boosters = 0x000000000009050f, - cf_syndicate_crawlers = 0x0000000000020f01, - cf_syndicate_dogtags = 0x0000000000000491, - cf_syndicate_heavymechs = 0x0000000000040f01, - cf_syndicate_mechs = 0x0000000000030f01, - cf_syndicate_robots = 0x0000000000000f01, - cf_syndicate_runners = 0x0000000000010f01, - cf_system_container = 0x0000000000010815, - cf_tactial_modules = 0x0000000000000a0f, - cf_target_painter = 0x00000000000b040f, - cf_teleport_back_home = 0x0000000004060278, - cf_teleport_channel_enabler = 0x0000000000000892, - cf_teleport_column = 0x0000000000000278, - cf_terraform_buoy = 0x0000000000000792, - cf_terraform_buoy_capsule = 0x0000000000000c98, - cf_terraform_equipment = 0x00000000000a060f, - cf_terraforming_ammo = 0x000000000000100a, - cf_terraforming_digger = 0x000000000002080f, - cf_terraforming_equipment = 0x000000000000080f, - cf_terraforming_raiser = 0x000000000003080f, - cf_test_robot_punchbags = 0x0000000000010a01, - cf_test_robots = 0x0000000000000a01, - cf_testnpc = 0x0000000000020a01, - cf_thelodica_commodities = 0x0000000000030214, - cf_thelodica_crawlers = 0x0000000000020c01, - cf_thelodica_dogtags = 0x0000000000000291, - cf_thelodica_egg_deployer = 0x0000000000030598, - cf_thelodica_energy_cores = 0x0000000000020514, - cf_thelodica_heavymechs = 0x0000000000040c01, - cf_thelodica_mechs = 0x0000000000030c01, - cf_thelodica_robots = 0x0000000000000c01, - cf_thelodica_robotshards = 0x0000000000020414, - cf_thelodica_runners = 0x0000000000010c01, - cf_thermal_armor_hardeners = 0x000000000404010f, - cf_thermal_kers = 0x00000000030b030f, - cf_tile_based = 0x0000000000010390, - cf_tracking_upgrades = 0x000000000007040f, - cf_training_docking_base = 0x0000000002010178, - cf_training_exit_teleport = 0x0000000000070278, - cf_training_items = 0x0000000000000992, - cf_turrets = 0x0000000000000801, - cf_tutorial_industry_items = 0x0000000000000295, - cf_tutorial_punchbag = 0x0000000000000496, - cf_vendors = 0x000000000000026e, - cf_visibility_based_probe = 0x0000000002010492, - cf_volume_wrapper_container = 0x0000000000000b15, - cf_volume_wrapper_container_old = 0x0000000000080815, - cf_walker_calibration_programs = 0x0000000005010416, - cf_walker_chassis = 0x0000000000050250, - cf_walker_head = 0x0000000000050150, - cf_walker_leg = 0x0000000000050350, - cf_walkers = 0x0000000000000601, - cf_walking_stabilizers = 0x000000000006030f, - cf_wall_builder_ammo = 0x000000000002110a, - cf_wall_healer = 0x0000000000020492, - cf_wall_healer_capsule = 0x0000000000000a98, - cf_weapon_calibration_programs = 0x0000000000020416, - cf_weapon_stabilizer = 0x000000000505070f, - cf_weapon_upgrade_calibration_programs = 0x0000000009020416, - cf_weapon_upgrades = 0x000000000005070f, - cf_weapons = 0x000000000000070f, - cf_webber = 0x000000000001050f, - cf_world_mobile_teleport_capsule = 0x0000000000020198, - cf_zone_item_shop = 0x0000000000000796, - cf_zone_objects = 0x0000000000000092, - def_pbs_dogtags = 0x0000000000000691 - } + [System.Flags] + public enum CategoryFlags : long + { + undefined = 0, + + cf_robots = 0x0000000000000001, + cf_noobot = 0x0000000000000101, + cf_runners = 0x0000000000000201, + cf_crawlers = 0x0000000000000301, + cf_mechs = 0x0000000000000401, + cf_heavy_mechs = 0x0000000000000501, + cf_walkers = 0x0000000000000601, + cf_gm_test = 0x0000000000000701, + cf_turrets = 0x0000000000000801, + cf_police_turret = 0x0000000000010801, + cf_hybrid_robot = 0x0000000000000901, + cf_test_robots = 0x0000000000000A01, + cf_test_robot_punchbags = 0x0000000000010A01, + cf_testnpc = 0x0000000000020A01, + cf_pelistal_robots = 0x0000000000000B01, + cf_pelistal_runners = 0x0000000000010B01, + cf_pelistal_crawlers = 0x0000000000020B01, + cf_pelistal_mechs = 0x0000000000030B01, + cf_pelistal_heavymechs = 0x0000000000040B01, + cf_thelodica_robots = 0x0000000000000C01, + cf_thelodica_runners = 0x0000000000010C01, + cf_thelodica_crawlers = 0x0000000000020C01, + cf_thelodica_mechs = 0x0000000000030C01, + cf_thelodica_heavymechs = 0x0000000000040C01, + cf_nuimqol_robots = 0x0000000000000D01, + cf_nuimqol_runners = 0x0000000000010D01, + cf_nuimqol_crawlers = 0x0000000000020D01, + cf_nuimqol_mechs = 0x0000000000030D01, + cf_nuimqol_heavymechs = 0x0000000000040D01, + cf_industrial_robots = 0x0000000000000E01, + cf_industrial_runners = 0x0000000000010E01, + cf_industrial_crawlers = 0x0000000000020E01, + cf_industrial_mechs = 0x0000000000030E01, + cf_industrial_heavymechs = 0x0000000000040E01, + cf_industrial_heavy_gliders = 0x0000000000050E01, + cf_syndicate_robots = 0x0000000000000F01, + cf_syndicate_runners = 0x0000000000010F01, + cf_syndicate_crawlers = 0x0000000000020F01, + cf_syndicate_mechs = 0x0000000000030F01, + cf_syndicate_heavymechs = 0x0000000000040F01, + cf_command_robots = 0x0000000000001001, + cf_combat_command_robots = 0x0000000000011001, + + cf_ammo = 0x000000000000000A, + cf_railgun_ammo = 0x000000000000010A, + cf_small_railgun_ammo = 0x000000000001010A, + cf_medium_railgun_ammo = 0x000000000002010A, + cf_large_railgun_ammo = 0x000000000003010A, + cf_laser_ammo = 0x000000000000020A, + cf_small_crystals = 0x000000000001020A, + cf_medium_crystals = 0x000000000002020A, + cf_large_crystals = 0x000000000003020A, + cf_projectile_ammo = 0x000000000000030A, + cf_small_projectile_ammo = 0x000000000001030A, + cf_medium_projectile_ammo = 0x000000000002030A, + cf_large_projectile_ammo = 0x000000000003030A, + cf_missile_ammo = 0x000000000000040A, + cf_small_missile_ammo = 0x000000000001040A, + cf_medium_missile_ammo = 0x000000000002040A, + cf_large_missile_ammo = 0x000000000003040A, + cf_mining_ammo = 0x000000000000050A, + cf_plasma_ammo = 0x000000000000060A, + cf_small_plasma_ammo = 0x000000000001060A, + cf_medium_plasma_ammo = 0x000000000002060A, + cf_large_plasma_ammo = 0x000000000003060A, + cf_core_booster_ammo = 0x000000000000070A, + cf_mining_probe_ammo = 0x000000000000090A, + cf_mining_probe_ammo_area = 0x000000000001090A, + cf_mining_probe_ammo_tile = 0x000000000002090A, + cf_mining_probe_ammo_one_tile = 0x000000000003090A, + cf_mining_probe_ammo_artifact = 0x000000000004090A, + cf_mining_probe_ammo_intrusion = 0x000000000005090A, + cf_mining_probe_ammo_direction = 0x000000000006090A, + cf_harvesting_ammo = 0x0000000000000A0A, + cf_ioncannon_ammo = 0x0000000000000B0A, + cf_small_ioncannon_ammo = 0x0000000000010B0A, + cf_medium_ioncannon_ammo = 0x0000000000020B0A, + cf_large_ioncannon_ammo = 0x0000000000030B0A, + cf_energytorpedo_ammo = 0x0000000000000C0A, + cf_small_energytorpedo_ammo = 0x0000000000010C0A, + cf_medium_energytorpedo_ammo = 0x0000000000020C0A, + cf_large_energytorpedo_ammo = 0x0000000000030C0A, + cf_industrial_aoe_tile_ammo = 0x0000000000000D0A, + cf_small_industrial_aoe_tile_ammo = 0x0000000000010D0A, + cf_medium_industrial_aoe_tile_ammo = 0x0000000000020D0A, + cf_large_industrial_aoe_tile_ammo = 0x0000000000030D0A, + cf_aoe_unit_ammo = 0x0000000000000E0A, + cf_small_aoe_unit_ammo = 0x0000000000010E0A, + cf_medium_aoe_unit_ammo = 0x0000000000020E0A, + cf_large_aoe_unit_ammo = 0x0000000000030E0A, + cf_blob_emission_modulator_ammo = 0x0000000000000F0A, + cf_terraforming_ammo = 0x000000000000100A, + cf_ammo_terraforming_multi = 0x000000000001100A, + cf_buoy_charger_ammo = 0x000000000002100A, + cf_construction_ammo_main = 0x000000000000110A, + cf_construction_ammo = 0x000000000001110A, + cf_wall_builder_ammo = 0x000000000002110A, + cf_remote_controlled_units = 0x000000000000120A, + cf_remote_controlled_turrets = 0x000000000001120A, + cf_sentry_turret_units = 0x000000000101120A, + cf_mining_turret_units = 0x000000000201120A, + cf_harvesting_turret_units = 0x000000000301120A, + cf_remote_controlled_combat_drones = 0x000000000002120A, + cf_pelistal_combat_drones_units = 0x000000000102120A, + cf_nuimqol_combat_drones_units = 0x000000000202120A, + cf_thelodica_combat_drones_units = 0x000000000302120A, + + cf_robot_equipment = 0x000000000000000F, + cf_armor_equipment = 0x000000000000010F, + cf_armor_plates = 0x000000000001010F, + cf_small_armor_plates = 0x000000000101010F, + cf_medium_armor_plates = 0x000000000201010F, + cf_large_armor_plates = 0x000000000301010F, + cf_armor_repair_systems = 0x000000000002010F, + cf_small_armor_repairers = 0x000000000102010F, + cf_medium_armor_repairers = 0x000000000202010F, + cf_large_armor_repairers = 0x000000000302010F, + cf_remote_armor_repairers = 0x000000000003010F, + cf_small_remote_armor_repairers = 0x000000000103010F, + cf_medium_remote_armor_repairers = 0x000000000203010F, + cf_large_remote_armor_repairers = 0x000000000303010F, + cf_armor_hardeners = 0x000000000004010F, + cf_chemical_armor_hardeners = 0x000000000104010F, + cf_kinetic_armor_hardeners = 0x000000000204010F, + cf_explosive_armor_hardeners = 0x000000000304010F, + cf_thermal_armor_hardeners = 0x000000000404010F, + cf_resistance_plating = 0x000000000504010F, + cf_lightweight_frame = 0x000000000005010F, + cf_armor_repair_upgrades = 0x000000000006010F, + cf_shield_equipment = 0x000000000000020F, + cf_shield_generators = 0x000000000001020F, + cf_small_shield_generator = 0x000000000101020F, + cf_medium_shield_generators = 0x000000000201020F, + cf_large_shield_generators = 0x000000000301020F, + cf_shield_hardener = 0x000000000002020F, + cf_engineering_equipment = 0x000000000000030F, + cf_energy_transferers = 0x000000000001030F, + cf_small_energy_transferers = 0x000000000101030F, + cf_medium_energy_transferers = 0x000000000201030F, + cf_large_energy_transferers = 0x000000000301030F, + cf_energy_vampires = 0x000000000002030F, + cf_small_energy_vampires = 0x000000000102030F, + cf_medium_energy_vampires = 0x000000000202030F, + cf_large_energy_vampires = 0x000000000302030F, + cf_energy_neutralizers = 0x000000000003030F, + cf_small_energy_neutralizers = 0x000000000103030F, + cf_medium_energy_neutralizers = 0x000000000203030F, + cf_large_energy_neutralizers = 0x000000000303030F, + cf_core_rechargers = 0x000000000004030F, + cf_powergrid_upgrades = 0x000000000005030F, + cf_walking_stabilizers = 0x000000000006030F, + cf_massmodifiers = 0x000000000007030F, + cf_core_batteries = 0x000000000008030F, + cf_small_core_batteries = 0x000000000108030F, + cf_medium_core_batteries = 0x000000000208030F, + cf_large_core_batteries = 0x000000000308030F, + cf_core_boosters = 0x000000000009030F, + cf_small_core_boosters = 0x000000000109030F, + cf_medium_core_boosters = 0x000000000209030F, + cf_large_core_boosters = 0x000000000309030F, + cf_maneuvering_equipment = 0x00000000000A030F, + cf_kers = 0x00000000000B030F, + cf_kinetic_kers = 0x00000000010B030F, + cf_explosive_kers = 0x00000000020B030F, + cf_thermal_kers = 0x00000000030B030F, + cf_neuralyzer = 0x00000000000C030F, + cf_energy_warfare_upgrades = 0x00000000000D030F, + cf_reactor_sealings = 0x00000000000E030F, + cf_landmine_detectors = 0x00000000000F030F, + cf_electronics_equipment = 0x000000000000040F, + cf_sensor_boosters = 0x000000000001040F, + cf_remote_sensor_boosters = 0x000000000002040F, + cf_cargo_scanner = 0x000000000003040F, + cf_chassis_scanner = 0x000000000004040F, + cf_industrial_scanner = 0x000000000005040F, + cf_cpu_upgrades = 0x000000000006040F, + cf_tracking_upgrades = 0x000000000007040F, + cf_stance_moduls = 0x000000000008040F, + cf_battle_stance_moduls = 0x000000000108040F, + cf_detection_modules = 0x000000000009040F, + cf_stealth_modules = 0x00000000000A040F, + cf_target_painter = 0x00000000000B040F, + cf_remote_controllers = 0x00000000000C040F, + cf_electronic_warfare_equipment = 0x000000000000050F, + cf_webber = 0x000000000001050F, + cf_shortrange_webber = 0x000000000101050F, + cf_longrange_webber = 0x000000000201050F, + cf_sensor_jammers = 0x000000000002050F, + cf_sensor_dampeners = 0x000000000003050F, + cf_eccm = 0x000000000004050F, + cf_aggrorange_decreasers = 0x000000000005050F, + cf_siege_hack_modules = 0x000000000006050F, + cf_blob_emission_modulator = 0x000000000007050F, + cf_ecm_boosters = 0x000000000008050F, + cf_supressor_boosters = 0x000000000009050F, + cf_gathering_equipment = 0x000000000000060F, + cf_drillers = 0x000000000001060F, + cf_small_drillers = 0x000000000101060F, + cf_medium_drillers = 0x000000000201060F, + cf_medium_drillers_old = 0x000000000002060F, + cf_industrial_turret_drillers = 0x000000000003060F, + cf_mining_upgrades = 0x000000000004060F, + cf_mining_probes = 0x000000000005060F, + cf_harvesters = 0x000000000006060F, + cf_small_harvesters = 0x000000000106060F, + cf_medium_harvesters = 0x000000000206060F, + cf_medium_harvesters_old = 0x000000000007060F, + cf_industrial_turret_harvesters = 0x000000000008060F, + cf_construction_modules = 0x000000000009060F, + cf_terraform_equipment = 0x00000000000A060F, + cf_weapons = 0x000000000000070F, + cf_lasers = 0x000000000001070F, + cf_small_lasers = 0x000000000101070F, + cf_medium_lasers = 0x000000000201070F, + cf_large_lasers = 0x000000000301070F, + cf_projectiles = 0x000000000002070F, + cf_single_projectile = 0x000000000402070F, + cf_small_single_projectile = 0x000000010402070F, + cf_medium_single_projectile = 0x000000020402070F, + cf_large_single_projectile = 0x000000030402070F, + cf_aoe_projectile = 0x000000000502070F, + cf_aoe_projectile_tile = 0x000000010502070F, + cf_small_aoe_projectile_tile = 0x000001010502070F, + cf_medium_aoe_tile = 0x000002010502070F, + cf_large_aoe_tile = 0x000003010502070F, + cf_aoe_projectile_unit = 0x000000020502070F, + cf_small_aoe_unit = 0x000001020502070F, + cf_medium_aoe_unit = 0x000002020502070F, + cf_large_aoe_unit = 0x000003020502070F, + cf_missiles = 0x000000000003070F, + cf_small_missile_launchers = 0x000000000103070F, + cf_medium_missile_launchers = 0x000000000203070F, + cf_large_missile_launchers = 0x000000000303070F, + cf_railguns = 0x000000000004070F, + cf_small_railguns = 0x000000000104070F, + cf_medium_railguns = 0x000000000204070F, + cf_large_railguns = 0x000000000304070F, + cf_weapon_upgrades = 0x000000000005070F, + cf_railgun_upgrades = 0x000000000105070F, + cf_projectile_upgrades = 0x000000000205070F, + cf_laser_upgrades = 0x000000000305070F, + cf_missile_upgrades = 0x000000000405070F, + cf_weapon_stabilizer = 0x000000000505070F, + cf_plasma = 0x000000000007070F, + cf_small_plasma = 0x000000000107070F, + cf_medium_plasma = 0x000000000307070F, + cf_large_plasma = 0x000000000407070F, + cf_ioncannon = 0x000000000008070F, + cf_small_ioncannon = 0x000000000108070F, + cf_medium_ioncannon = 0x000000000208070F, + cf_large_ioncannon = 0x000000000308070F, + cf_energytorpedo = 0x000000000009070F, + cf_small_energytorpedo = 0x000000000109070F, + cf_medium_energytorpedo = 0x000000000209070F, + cf_large_energytorpedo = 0x000000000309070F, + cf_industrial_aoe = 0x00000000000A070F, + cf_small_industrial_aoe = 0x00000000010A070F, + cf_medium_industrial_aoe = 0x00000000020A070F, + cf_large_industrial_aoe = 0x00000000030A070F, + cf_sentry_turret_guns = 0x00000000000C070F, + cf_terraforming_equipment = 0x000000000000080F, + cf_small_terraformer = 0x000000000001080F, + cf_terraforming_digger = 0x000000000002080F, + cf_terraforming_raiser = 0x000000000003080F, + cf_gang_assist_modules = 0x000000000000090F, + cf_gang_assist_speed = 0x000000000001090F, + cf_gang_assist_siege = 0x000000000002090F, + cf_gang_assist_ewar = 0x000000000003090F, + cf_gang_assist_defense = 0x000000000004090F, + cf_gang_assist_industry = 0x000000000005090F, + cf_gang_assist_information = 0x000000000006090F, + cf_gang_assist_shared_dataprocessing = 0x000000000007090F, + cf_gang_assist_coordinated_manuevering = 0x000000000008090F, + cf_gang_assist_maintance = 0x000000000009090F, + cf_gang_assist_precision_firing = 0x00000000000A090F, + cf_gang_assist_devastating = 0x00000000000B090F, + cf_gang_assist_core_management = 0x00000000000C090F, + cf_gang_assist_fast_extracting = 0x00000000000D090F, + cf_gang_assist_shield_calculations = 0x00000000000E090F, + cf_gang_assist_resilience = 0x00000000000F090F, + cf_tactial_modules = 0x0000000000000A0F, + cf_ew_resist = 0x0000000000010A0F, + cf_lock_breaker = 0x0000000000020A0F, + cf_overcharged_armor_resist = 0x0000000000030A0F, + + cf_material = 0x0000000000000014, + cf_raw_material = 0x0000000000000114, + cf_organic = 0x0000000000010114, + cf_ore = 0x0000000000020114, + cf_liquid = 0x0000000000040114, + cf_geothermal = 0x0000000000050114, + cf_basic_commodities = 0x0000000000000214, + cf_primary_commodities = 0x0000000000010214, + cf_pelistal_commodities = 0x0000000000020214, + cf_thelodica_commodities = 0x0000000000030214, + cf_nuimqol_commodities = 0x0000000000040214, + cf_hightier_commodities = 0x0000000000050214, + cf_gamma_commodities = 0x0000000000060214, + cf_soil = 0x0000000000000314, + cf_robotshards = 0x0000000000000414, + cf_common_robotshards = 0x0000000000010414, + cf_thelodica_robotshards = 0x0000000000020414, + cf_nuimqol_robotshards = 0x0000000000030414, + cf_pelistal_robotshards = 0x0000000000040414, + cf_energy_cores = 0x0000000000000514, + cf_common_energy_cores = 0x0000000000010514, + cf_thelodica_energy_cores = 0x0000000000020514, + cf_pelistal_energy_cores = 0x0000000000030514, + cf_nuimqol_energy_cores = 0x0000000000040514, + cf_reactor_plasma = 0x0000000000000614, + cf_rare_material = 0x0000000000000714, + cf_artifact_data = 0x0000000000000914, + + cf_container = 0x0000000000000015, + cf_infinite_capacity_containers = 0x0000000000000815, + cf_system_container = 0x0000000000010815, + cf_loot = 0x0000000000020815, + cf_infinite_capacity_box = 0x0000000000030815, + cf_public_container = 0x0000000000040815, + cf_public_corporation_hangar_storage = 0x0000000000050815, + cf_corporate_hangar = 0x0000000000060815, + cf_corporate_hangar_folder = 0x0000000000070815, + cf_volume_wrapper_container_old = 0x0000000000080815, + cf_limited_capacity_containers = 0x0000000000000915, + cf_private_container = 0x0000000000010915, + cf_robot_inventory = 0x0000000000030915, + cf_limited_capacity_box = 0x0000000000040915, + cf_lootcontainers = 0x0000000000000A15, + cf_lootonly_containers = 0x0000000000010A15, + cf_field_containers = 0x0000000000020A15, + cf_volume_wrapper_container = 0x0000000000000B15, + + cf_documents = 0x0000000000000016, + cf_mining_probe_result = 0x0000000000000316, + cf_mining_probe_result_area = 0x0000000000010316, + cf_mining_probe_result_tile = 0x0000000000020316, + cf_calibration_programs = 0x0000000000000416, + cf_robot_calibration_programs = 0x0000000000010416, + cf_runner_calibration_programs = 0x0000000001010416, + cf_crawler_calibration_programs = 0x0000000002010416, + cf_mech_calibration_programs = 0x0000000003010416, + cf_heavymech_calibration_programs = 0x0000000004010416, + cf_walker_calibration_programs = 0x0000000005010416, + cf_weapon_calibration_programs = 0x0000000000020416, + cf_railgun_calibration_programs = 0x0000000001020416, + cf_laser_calibration_programs = 0x0000000002020416, + cf_plasmagun_calibration_programs = 0x0000000003020416, + cf_missile_calibration_programs = 0x0000000004020416, + cf_projectile_calibration_programs = 0x0000000005020416, + cf_ioncannon_calibration_programs = 0x0000000006020416, + cf_energytorpedo_calibration_programs = 0x0000000007020416, + cf_aoe_calibration_programs = 0x0000000008020416, + cf_weapon_upgrade_calibration_programs = 0x0000000009020416, + cf_industrial_aoe_calibration_programs = 0x000000000A020416, + cf_ammo_calibration_programs = 0x0000000000030416, + cf_ammo_railgun_calibration_programs = 0x0000000001030416, + cf_ammo_laser_calibration_programs = 0x0000000002030416, + cf_ammo_missile_calibration_programs = 0x0000000004030416, + cf_ammo_projectile_calibration_programs = 0x0000000005030416, + cf_ammo_harvesting_calibration_programs = 0x0000000007030416, + cf_ammo_plasma_calibration_programs = 0x0000000008030416, + cf_ammo_energy_torpedo_calibration_programs = 0x0000000009030416, + cf_ammo_ioncannon_calibration_programs = 0x000000000A030416, + cf_ammo_corebooster_calibration_programs = 0x000000000B030416, + cf_ammo_mining_calibration_programs = 0x000000000C030416, + cf_ammo_industrial_aoe_calibration_programs = 0x000000000D030416, + cf_ammo_mining_probe_calibration_programs = 0x000000000E030416, + cf_ammo_rcu_calibration_programs = 0x000000000F030416, + cf_module_calibration_programs = 0x0000000000040416, + cf_armor_calibration_programs = 0x0000000001040416, + cf_ew_calibration_programs = 0x0000000002040416, + cf_engineering_calibration_programs = 0x0000000003040416, + cf_industry_calibration_programs = 0x0000000004040416, + cf_gang_assist_calibration_programs = 0x0000000005040416, + cf_electronics_equipment_calibration_programs = 0x0000000006040416, + cf_robot_component_calibration_programs = 0x0000000000050416, + cf_head_calibration_programs = 0x0000000001050416, + cf_chassis_calibration_programs = 0x0000000002050416, + cf_leg_calibration_programs = 0x0000000003050416, + cf_mission_calibration_programs = 0x0000000000060416, + cf_pbs_calibration_programs = 0x0000000000070416, + cf_random_calibration_programs = 0x0000000000080416, + + cf_logical_storage = 0x0000000000000017, + + cf_robot_components = 0x0000000000000050, + cf_robot_head = 0x0000000000000150, + cf_runner_head = 0x0000000000010150, + cf_crawler_head = 0x0000000000020150, + cf_mech_head = 0x0000000000030150, + cf_heavymech_head = 0x0000000000040150, + cf_walker_head = 0x0000000000050150, + cf_robot_chassis = 0x0000000000000250, + cf_runner_chassis = 0x0000000000010250, + cf_crawler_chassis = 0x0000000000020250, + cf_mech_chassis = 0x0000000000030250, + cf_heavymech_chassis = 0x0000000000040250, + cf_walker_chassis = 0x0000000000050250, + cf_robot_leg = 0x0000000000000350, + cf_runner_leg = 0x0000000000010350, + cf_crawler_leg = 0x0000000000020350, + cf_mech_leg = 0x0000000000030350, + cf_heavymech_leg = 0x0000000000040350, + cf_walker_leg = 0x0000000000050350, + + cf_decor = 0x000000000000005A, + cf_rock = 0x000000000000015A, + cf_decor_nuimqol_misc = 0x000000000000025A, + cf_decor_thelodica_misc = 0x000000000000035A, + cf_decor_pelistal_misc = 0x000000000000045A, + cf_decor_buildings = 0x000000000000055A, + cf_decor_general = 0x000000000001055A, + cf_decor_general_element = 0x000000000101055A, + cf_decor_pelistal = 0x000000000002055A, + cf_decor_pelistal_general = 0x000000000102055A, + cf_decor_pelistal_special = 0x000000000202055A, + cf_decor_pelistal_element = 0x000000000302055A, + cf_decor_nuimqol = 0x000000000003055A, + cf_decor_nuimqol_general = 0x000000000103055A, + cf_decor_nuimqol_special = 0x000000000203055A, + cf_decor_nuimqol_element = 0x000000000303055A, + cf_decor_thelodica = 0x000000000004055A, + cf_decor_thelodica_general = 0x000000000104055A, + cf_decor_thelodica_special = 0x000000000204055A, + cf_decor_thelodica_element = 0x000000000304055A, + cf_decor_template = 0x000000000000065A, + + cf_station_services = 0x000000000000006E, + cf_markets = 0x000000000000016E, + cf_public_market = 0x000000000001016E, + cf_base_item_shop = 0x000000000002016E, + cf_public_vendor = 0x000000000001026E, + cf_vendors = 0x000000000000026E, + cf_production_facilities = 0x000000000000036E, + cf_refinery_facility = 0x000000000001036E, + cf_outpost_refinery = 0x000000000101036E, + cf_pbs_refinery = 0x000000000201036E, + cf_reprocessor_facility = 0x000000000004036E, + cf_outpost_reprocessor = 0x000000000104036E, + cf_pbs_reprocessor = 0x000000000204036E, + cf_repair_facility = 0x000000000005036E, + cf_outpost_repair = 0x000000000105036E, + cf_pbs_repair = 0x000000000205036E, + cf_research_lab = 0x000000000006036E, + cf_outpost_research_lab = 0x000000000106036E, + cf_pbs_reseach_lab = 0x000000000206036E, + cf_robot_assembler = 0x000000000007036E, + cf_mill = 0x000000000008036E, + cf_outpost_mill = 0x000000000108036E, + cf_pbs_mill = 0x000000000208036E, + cf_prototyper = 0x000000000009036E, + cf_outpost_prototyper = 0x000000000109036E, + cf_pbs_prototyper = 0x000000000209036E, + cf_insurance_facility = 0x00000000000A036E, + cf_research_kit_forge = 0x00000000000B036E, + cf_calibration_program_forge = 0x00000000000C036E, + cf_mission_agent = 0x000000000000046E, + + cf_structures = 0x0000000000000078, + cf_base = 0x0000000000000178, + cf_public_docking_base = 0x0000000000010178, + cf_outpost = 0x0000000001010178, + cf_training_docking_base = 0x0000000002010178, + cf_field_terminal = 0x0000000000020178, + cf_teleport_column = 0x0000000000000278, + cf_public_teleport_column = 0x0000000000010278, + cf_dynamic_teleports = 0x0000000000060278, + cf_mobile_teleport = 0x0000000001060278, + cf_mobile_world_teleport = 0x0000000002060278, + cf_mobile_arena_teleport = 0x0000000003060278, + cf_teleport_back_home = 0x0000000004060278, + cf_training_exit_teleport = 0x0000000000070278, + + cf_groups = 0x0000000000000082, + cf_corporation = 0x0000000000000182, + cf_default_corporation = 0x0000000000010182, + cf_private_corporation = 0x0000000000020182, + cf_alliance = 0x0000000000000282, + cf_private_alliance = 0x0000000000010282, + cf_default_alliance = 0x0000000000020282, + + cf_player = 0x000000000000008C, + + cf_police = 0x000000000000008E, + + cf_npc = 0x000000000000008F, + cf_npc_police = 0x000000000000018F, + cf_npc_test = 0x000000000000028F, + cf_npc_thelodica = 0x000000000000038F, + cf_npc_nuimqol = 0x000000000000048F, + cf_npc_pelistal = 0x000000000000058F, + cf_npc_indy = 0x000000000000068F, + + cf_scan_result = 0x0000000000000090, + cf_material_scan_result = 0x0000000000000390, + cf_tile_based = 0x0000000000010390, + cf_area_based = 0x0000000000020390, + + cf_dogtags = 0x0000000000000091, + cf_pelistal_dogtags = 0x0000000000000191, + cf_thelodica_dogtags = 0x0000000000000291, + cf_nuimqol_dogtags = 0x0000000000000391, + cf_syndicate_dogtags = 0x0000000000000491, + cf_indy_dogtags = 0x0000000000000591, + def_pbs_dogtags = 0x0000000000000691, + + cf_zone_objects = 0x0000000000000092, + cf_loot_container_object = 0x0000000000000192, + cf_blob_emitter = 0x0000000000000292, + cf_eggs = 0x0000000000000392, + cf_npc_eggs = 0x0000000000010392, + cf_area_bomb = 0x0000000000020392, + cf_intrusion_pbs_eggs = 0x0000000000040392, + cf_pbs_egg = 0x0000000000050392, + cf_player_built = 0x0000000000000492, + cf_proximity_probes = 0x0000000000010492, + cf_proximity_probe = 0x0000000002010492, + cf_wall_healer = 0x0000000000020492, + cf_pbs = 0x0000000000030492, + cf_pbs_reactor = 0x0000000001030492, + cf_pbs_core_transmitter = 0x0000000002030492, + cf_pbs_production_nodes = 0x0000000003030492, + cf_pbs_reseach_lab_nodes = 0x0000000103030492, + cf_pbs_repair_nodes = 0x0000000203030492, + cf_pbs_production_upgrade_nodes = 0x0000000303030492, + cf_pbs_prototyper_nodes = 0x0000000403030492, + cf_pbs_mill_nodes = 0x0000000503030492, + cf_pbs_refinery_nodes = 0x0000000603030492, + cf_pbs_reprocessor_nodes = 0x0000000703030492, + cf_pbs_calibration_forge_nodes = 0x0000000803030492, + cf_pbs_research_kit_forge_nodes = 0x0000000903030492, + cf_pbs_effect_supplier = 0x0000000004030492, + cf_pbs_effect_emitter = 0x0000000005030492, + cf_pbs_turret = 0x0000000006030492, + cf_pbs_armor_repairer = 0x0000000007030492, + cf_pbs_control_tower = 0x0000000008030492, + cf_pbs_docking_base = 0x0000000009030492, + cf_pbs_energy_well = 0x000000000A030492, + cf_pbs_highway_node = 0x000000000B030492, + cf_pbs_mining_towers = 0x000000000C030492, + cf_landmines = 0x00000000040492, + cf_light_landmines = 0x0000000001040492, + cf_medium_landmines = 0x0000000002040492, + cf_heavy_landmines = 0x0000000003040492, + cf_intrusion_objects = 0x0000000000000592, + cf_destrucion_sap = 0x0000000000010592, + cf_specimen_processing_sap = 0x0000000000020592, + cf_active_hacking_sap = 0x0000000000030592, + cf_passive_hacking_sap = 0x0000000000040592, + cf_intrusion_turrets = 0x0000000000050592, + cf_arena_guard = 0x0000000000000692, + cf_terraform_buoy = 0x0000000000000792, + cf_teleport_channel_enabler = 0x0000000000000892, + cf_training_items = 0x0000000000000992, + cf_gate = 0x0000000000000A92, + cf_rifts = 0x0000000000000B92, + cf_portals = 0x0000000000000C92, + cf_sentry_turrets = 0x0000000000000D92, + cf_mining_turrets = 0x0000000000000E92, + cf_harvesting_turrets = 0x0000000000000F92, + cf_combat_drones = 0x0000000000001092, + + cf_production_items = 0x0000000000000094, + cf_research_kits = 0x0000000000000194, + cf_reactor_cores = 0x0000000000000294, + cf_pbs_reactor_booster = 0x0000000000000394, + cf_kernels = 0x0000000000000494, + cf_random_research_kits = 0x0000000000000594, + cf_datashards = 0x0000000000000694, + + cf_mission_items = 0x0000000000000095, + cf_courier_items = 0x0000000000000195, + cf_tutorial_industry_items = 0x0000000000000295, + cf_starter_item = 0x0000000000000395, + cf_mission_mineral = 0x0000000000000495, + cf_random_items = 0x0000000000000595, + cf_generic_random_items = 0x0000000000010595, + cf_component_items = 0x0000000000020595, + cf_random_artifacts = 0x0000000000030595, + cf_geoscan_documents = 0x0000000000000695, + cf_intel_documents = 0x0000000000000795, + + cf_items = 0x0000000000000096, + cf_admin_items = 0x0000000000000196, + cf_others = 0x0000000000000296, + cf_item_speed_boosters = 0x0000000000000396, + cf_tutorial_punchbag = 0x0000000000000496, + cf_mission_structures = 0x0000000000000696, + cf_kiosk = 0x0000000000010696, + cf_mission_switch = 0x0000000000040696, + cf_alarm_switch = 0x0000000001040696, + cf_simple_switch = 0x0000000002040696, + cf_item_supply = 0x0000000000050696, + cf_zone_item_shop = 0x0000000000000796, + cf_gift_packages = 0x0000000000000896, + cf_consumable_items = 0x0000000000000996, + cf_consumable_boosters = 0x0000000000010996, + + cf_field_accessories = 0x0000000000000098, + cf_mobile_teleport_capsule = 0x0000000000000198, + cf_inland_mobile_teleport_capsule = 0x0000000000010198, + cf_world_mobile_teleport_capsule = 0x0000000000020198, + cf_container_capsule = 0x0000000000000298, + cf_plant_seed = 0x0000000000000398, + cf_egg_deployers = 0x0000000000000498, + cf_area_bomb_deployer = 0x0000000000020498, + cf_intrusion_pbs_deployers = 0x0000000000040498, + cf_npc_egg_deployer = 0x0000000000000598, + cf_nuimqol_egg_deployer = 0x0000000000010598, + cf_pelistal_egg_deployer = 0x0000000000020598, + cf_thelodica_egg_deployer = 0x0000000000030598, + cf_industrial_egg_deployer = 0x0000000000040598, + cf_mission_egg_deployer = 0x0000000000050598, + cf_arena_teleport_deployer = 0x0000000000000698, + cf_proximity_probe_deployer = 0x0000000000000798, + cf_plasma_bomb = 0x0000000000000898, + cf_deployable_structure = 0x0000000000000998, + cf_wall_healer_capsule = 0x0000000000000A98, + cf_terraform_buoy_capsule = 0x0000000000000C98, + cf_landmine_deployer = 0x0000000000000D98, + + cf_dynamic_cprg = 0x0000000000000099, + + cf_pbs_capsules = 0x000000000000009A, + cf_pbs_docking_base_capsules = 0x000000000000019A, + cf_pbs_reactor_capsules = 0x000000000000029A, + cf_pbs_core_transmitter_capsules = 0x000000000000039A, + cf_pbs_basic_transmitter_capsules = 0x000000000001039A, + cf_pbs_XL_transmitter_capsules = 0x000000000002039A, + cf_pbs_battery_capsules = 0x000000000003039A, + cf_pbs_production_capsules = 0x000000000000049A, + cf_pbs_production_refinery_capsules = 0x000000000001049A, + cf_pbs_production_reprocessor_capsules = 0x000000000002049A, + cf_pbs_production_repair_capsules = 0x000000000003049A, + cf_pbs_production_prototyper_capsules = 0x000000000004049A, + cf_pbs_production_research_lab_capsules = 0x000000000005049A, + cf_pbs_production_mill_capsules = 0x000000000006049A, + cf_pbs_production_ct_merge_capsules = 0x000000000007049A, + cf_pbs_production_decoder_complex_capsules = 0x000000000008049A, + cf_pbs_production_upgrade_capsules = 0x0000000000000C9A, + cf_pbs_effect_supplier_capsules = 0x0000000000000D9A, + cf_pbs_effect_emitter_capsules = 0x0000000000000E9A, + cf_pbs_miner_tower_capsules = 0x0000000000010E9A, + cf_pbs_masker_tower_capsules = 0x0000000000020E9A, + cf_pbs_aura_emitter_capsules = 0x0000000000030E9A, + cf_pbs_repairer_capsules = 0x0000000000000F9A, + cf_pbs_turret_capsules = 0x000000000000109A, + cf_pbs_railgun_turret_capsules = 0x000000000001109A, + cf_pbs_laser_turret_capsules = 0x000000000002109A, + cf_pbs_missile_turret_capsules = 0x000000000003109A, + cf_pbs_ew_turret_capsules = 0x000000000004109A, + cf_pbs_control_tower_capsules = 0x000000000000119A, + cf_pbs_energy_well_capsules = 0x000000000000129A, + cf_pbs_highway_node_capsule = 0x000000000000139A, + + cf_redeemables = 0x000000000000009B, + cf_package_activator = 0x000000000000019B, + cf_package_activator_ep = 0x000000000001019B, + cf_package_activator_spark = 0x000000000002019B, + cf_package_activator_credit = 0x000000000003019B, + cf_ice = 0x000000000000029B, + cf_mission_coin = 0x000000000000039B, + cf_lottery_items = 0x000000000000049B, + cf_ep_boosters = 0x000000000001049B, + cf_paints = 0x000000000002049B, + cf_ct_capsules = 0x000000000003049B, + cf_ct_capsules_ammo = 0x000000000103049B, + cf_ct_capsules_ammo_laser = 0x000000010103049B, + cf_ct_capsules_ammo_railgun = 0x000000020103049B, + cf_ct_capsules_ammo_projectile = 0x000000030103049B, + cf_ct_capsules_ammo_missile = 0x000000040103049B, + cf_ct_capsules_equipment = 0x000000000203049B, + cf_ct_capsules_equipment_armor = 0x000000010203049B, + cf_ct_capsules_equipment_ewar = 0x000000020203049B, + cf_ct_capsules_equipment_electronics = 0x000000030203049B, + cf_ct_capsules_equipment_engineering = 0x000000040203049B, + cf_ct_capsules_equipment_industrial = 0x000000050203049B, + cf_ct_capsules_equipment_shield = 0x000000060203049B, + cf_ct_capsules_equipment_weapons = 0x000000070203049B, + cf_ct_capsules_equipment_nexus = 0x000000080203049B, + cf_ct_capsules_robot = 0x000000000303049B, + cf_ct_capsules_robot_pelistal = 0x000000010303049B, + cf_ct_capsules_robot_pelistal_light = 0x000001010303049B, + cf_ct_capsules_robot_pelistal_assault = 0x000002010303049B, + cf_ct_capsules_robot_pelistal_mech = 0x000003010303049B, + cf_ct_capsules_robot_pelistal_heavymech = 0x000004010303049B, + cf_ct_capsules_robot_thelodica = 0x000000020303049B, + cf_ct_capsules_robot_thelodica_light = 0x000001020303049B, + cf_ct_capsules_robot_thelodica_assault = 0x000002020303049B, + cf_ct_capsules_robot_thelodica_mech = 0x000003020303049B, + cf_ct_capsules_robot_thelodica_heavymech = 0x000004020303049B, + cf_ct_capsules_robot_nuimqol = 0x000000030303049B, + cf_ct_capsules_robot_nuimqol_light = 0x000001030303049B, + cf_ct_capsules_robot_nuimqol_assault = 0x000002030303049B, + cf_ct_capsules_robot_nuimqol_mech = 0x000003030303049B, + cf_ct_capsules_robot_nuimqol_heavymech = 0x000004030303049B, + cf_ct_capsules_robot_industrial = 0x000000040303049B, + cf_ct_capsules_robot_industrial_light = 0x000001040303049B, + cf_ct_capsules_robot_industrial_assault = 0x000002040303049B, + cf_ct_capsules_robot_industrial_mech = 0x000003040303049B, + cf_ct_capsules_robot_industrial_heavymech = 0x000004040303049B, + cf_ct_capsules_robot_syndicate = 0x000000050303049B, + cf_ct_capsules_robot_syndicate_light = 0x000001050303049B, + cf_ct_capsules_robot_syndicate_assault = 0x000002050303049B, + cf_ct_capsules_robot_syndicate_mech = 0x000003050303049B, + cf_ct_capsules_robot_syndicate_heavymech = 0x000004050303049B, + cf_ct_capsules_robot_syndicate_commandbot = 0x000005050303049B, + cf_ct_capsules_misc = 0x000000000403049B, + cf_respec_tokens = 0x000000000004049B, + cf_spark_teleport_devices = 0x000000000005049B, + } } diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 48cf19f94..9b54399e8 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -480,6 +480,10 @@ public static class DefinitionNames public const string CONSTRUCTION_MODULE_AMMO_NORMAL = "def_construction_module_ammo_normal"; public const string CONSTRUCTION_MODULE_AMMO_NORMAL_CPRG = "def_construction_module_ammo_normal_cprg"; public const string CONSTRUCTION_MODULE_AMMO_SYNDICATE = "def_construction_module_ammo_syndicate"; + public const string CONSTRUCTION_MODULE_AMMO_T0 = "def_construction_module_ammo_t0"; + public const string CONSTRUCTION_MODULE_AMMO_T1 = "def_construction_module_ammo_t1"; + public const string CONSTRUCTION_MODULE_AMMO_T2 = "def_construction_module_ammo_t2"; + public const string CONSTRUCTION_MODULE_AMMO_T3 = "def_construction_module_ammo_t3"; public const string CONTAINER_CAPSULE = "def_container_capsule"; public const string COREBOOSTER_AMMO = "def_corebooster_ammo"; public const string COREBOOSTER_AMMO_CPRG = "def_corebooster_ammo_cprg"; @@ -970,6 +974,7 @@ public static class DefinitionNames public const string FIELD_CONTAINER = "def_field_container"; public const string FIELD_TERMINAL_NUIMQOL = "def_field_terminal_nuimqol"; public const string FIELD_TERMINAL_PELISTAL = "def_field_terminal_pelistal"; + public const string FIELD_TERMINAL_RALLY = "def_field_terminal_rally"; public const string FIELD_TERMINAL_THELODICA = "def_field_terminal_thelodica"; public const string GALATROY_BOT = "def_galatroy_bot"; public const string GALATROY_CHASSIS = "def_galatroy_chassis"; @@ -1058,7 +1063,10 @@ public static class DefinitionNames public const string HYBRID_ROBOT = "def_hybrid_robot"; public const string HYDROBENOL = "def_hydrobenol"; public const string ICE = "def_ice"; - public const string ICS_MISSION_COIN = "def_ics_mission_coin"; + + public const string RESPEC_TOKEN = "def_respec_token"; + + public const string ICS_MISSION_COIN = "def_ics_mission_coin"; public const string ICS_ZONE_ITEM_SHOP = "def_ics_zone_item_shop"; public const string ICTUS_BOT = "def_ictus_bot"; public const string ICTUS_BOT_CPRG = "def_ictus_bot_cprg"; @@ -3966,6 +3974,7 @@ public static class DefinitionNames public const string PBS_ENERGYWELL_SMALL_CAPSULE_CPRG = "def_pbs_energywell_small_capsule_cprg"; public const string PBS_ENERGYWELL_SMALL_OBJECT = "def_pbs_energywell_small_object"; public const string PBS_EW_TURRET_BOT = "def_pbs_ew_turret_bot"; + public const string PBS_EXPIRING_DOCKING_BASE = "def_pbs_expiring_docking_base"; public const string PBS_FACILITY_CALIBRATION_PROGRAM_FORGE = "def_pbs_facility_calibration_program_forge"; public const string PBS_FACILITY_MILL = "def_pbs_facility_mill"; public const string PBS_FACILITY_PROTOTYPER = "def_pbs_facility_prototyper"; @@ -4351,7 +4360,9 @@ public static class DefinitionNames public const string REDEEMABLE_EP_STANDARD = "def_redeemable_ep_standard"; public const string REDEEMABLE_SPARK_DLC0 = "def_redeemable_spark_dlc0"; public const string REDEEMABLE_SPARK_DLC1 = "def_redeemable_spark_dlc1"; - public const string RESEARCH_KIT_0 = "def_research_kit_0"; + public const string RELIC = "def_relic"; + public const string RELIC_SAP = "def_relic_sap"; + public const string RESEARCH_KIT_0 = "def_research_kit_0"; public const string RESEARCH_KIT_1 = "def_research_kit_1"; public const string RESEARCH_KIT_10 = "def_research_kit_10"; public const string RESEARCH_KIT_2 = "def_research_kit_2"; @@ -4510,6 +4521,7 @@ public static class DefinitionNames public const string STANDARD_ARMOR_REPAIRER_UPGRADE_CPRG = "def_standard_armor_repairer_upgrade_cprg"; public const string STANDARD_BATTLE_STANCE_MODUL = "def_standard_battle_stance_modul"; public const string STANDARD_BATTLE_STANCE_MODUL_CPRG = "def_standard_battle_stance_modul_cprg"; + public const string STANDARD_BLINDER_MODULE = "def_standard_blinder"; public const string STANDARD_BLOB_EMISSION_MODULATOR = "def_standard_blob_emission_modulator"; public const string STANDARD_BLOB_EMISSION_MODULATOR_CPRG = "def_standard_blob_emission_modulator_cprg"; public const string STANDARD_CARGO_SCANNER = "def_standard_cargo_scanner"; @@ -4716,6 +4728,7 @@ public static class DefinitionNames public const string STERMONIT_RARE_LARGE = "def_stermonit_rare_large"; public const string STERMONIT_RARE_MEDIUM = "def_stermonit_rare_medium"; public const string STERMONIT_RARE_SMALL = "def_stermonit_rare_small"; + public const string STRONGHOLD_EXIT_RIFT = "def_stronghold_exit_rift"; public const string SYMBIONT_BOT = "def_symbiont_bot"; public const string SYMBIONT_BOT_CPRG = "def_symbiont_bot_cprg"; public const string SYMBIONT_BOT_PR = "def_symbiont_bot_pr"; @@ -4743,6 +4756,7 @@ public static class DefinitionNames public const string SYNDICATE_POLICE_HEAD = "def_syndicate_police_head"; public const string SYNDICATE_POLICE_LEG = "def_syndicate_police_leg"; public const string SYSTEM_CONTAINER = "def_system_container"; + public const string TARGETTED_RIFT = "def_rift_targetted_portal"; public const string TELEPORT_BACK_HOME = "def_teleport_back_home"; public const string TELEPORT_COLUMN_STORAGE = "def_teleport_column_storage"; public const string TELEPORT_ENABLER = "def_teleport_enabler"; diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 3f0115b6e..836fa5ec6 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -4,53 +4,56 @@ namespace Perpetuum.ExportedTypes { - [System.Flags] - public enum EffectCategory : long - { - undefined = 0, - effcat_aggressor = 0x0000000000004000, - effcat_blob_emission_modulator = 0x0000000010000000, - effcat_consumable_effects = 0x0000008000000000, - effcat_detection_stealth = 0x0000000020000000, - effcat_eccm = 0x0000000100000000, - effcat_eccmcureable_effects = 0x0000000200000000, - effcat_gang_effect = 0x0000000000000040, - effcat_gang_effect_coordinated_maneuvering = 0x0000000000100000, - effcat_gang_effect_core_management = 0x0000000001000000, - effcat_gang_effect_defense = 0x0000000000000080, - effcat_gang_effect_ewar = 0x0000000000040000, - effcat_gang_effect_fast_extraction = 0x0000000002000000, - effcat_gang_effect_gathering = 0x0000000000000400, - effcat_gang_effect_information = 0x0000000000000100, - effcat_gang_effect_maintance = 0x0000000000400000, - effcat_gang_effect_precision_firing = 0x0000000000800000, - effcat_gang_effect_shared_dataprocessing = 0x0000000000080000, - effcat_gang_effect_shield_calculations = 0x0000000000200000, - effcat_gang_effect_siege = 0x0000000000000800, - effcat_gang_effect_speed = 0x0000000000000200, - effcat_highway = 0x0000000080000000, - effcat_intrusion_effect = 0x0000000800000000, - effcat_invulnerable = 0x0000000000010000, - effcat_jamm = 0x0000000000001000, - effcat_movement_imparing = 0x0000000000000001, - effcat_pbs_booster_effects = 0x0000001000000000, - effcat_pbs_engineering_aura = 0x0000020000000000, - effcat_pbs_gap_generator_effect = 0x0000002000000000, - effcat_pbs_industry_aura = 0x0000040000000000, - effcat_pbs_mining_tower_effect = 0x0000004000000000, - effcat_pbs_sensors_aura = 0x0000010000000000, - effcat_pvp_flag = 0x0000000000002000, - effcat_range_increasing = 0x0000000000000020, - effcat_resists = 0x0000000000000008, - effcat_safe_spot = 0x0000000004000000, - effcat_sensor_amplify = 0x0000000000000002, - effcat_sensor_supress = 0x0000000000000004, - effcat_shield = 0x0000000000000010, - effcat_speed_booster = 0x0000000000008000, - effcat_tag = 0x0000000000020000, - effcat_target_paint = 0x0000000040000000, - effcat_teleport_effects = 0x0000000008000000, - effcat_terrain_object_effects = 0x0000080000000000, - effcat_zero_core_drop = 0x0000000400000000 - } + [System.Flags] + public enum EffectCategory : long + { + undefined = 0, + effcat_movement_imparing = 0x0000000000000001, + effcat_sensor_amplify = 0x0000000000000002, + effcat_sensor_supress = 0x0000000000000004, + effcat_resists = 0x0000000000000008, + effcat_shield = 0x0000000000000010, + effcat_range_increasing = 0x0000000000000020, + effcat_gang_effect = 0x0000000000000040, + effcat_gang_effect_defense = 0x0000000000000080, + effcat_gang_effect_information = 0x0000000000000100, + effcat_gang_effect_speed = 0x0000000000000200, + effcat_gang_effect_gathering = 0x0000000000000400, + effcat_gang_effect_siege = 0x0000000000000800, + effcat_jamm = 0x0000000000001000, + effcat_pvp_flag = 0x0000000000002000, + effcat_aggressor = 0x0000000000004000, + effcat_speed_booster = 0x0000000000008000, + effcat_invulnerable = 0x0000000000010000, + effcat_tag = 0x0000000000020000, + effcat_gang_effect_ewar = 0x0000000000040000, + effcat_gang_effect_shared_dataprocessing = 0x0000000000080000, + effcat_gang_effect_coordinated_maneuvering = 0x0000000000100000, + effcat_gang_effect_shield_calculations = 0x0000000000200000, + effcat_gang_effect_maintance = 0x0000000000400000, + effcat_gang_effect_precision_firing = 0x0000000000800000, + effcat_gang_effect_core_management = 0x0000000001000000, + effcat_gang_effect_fast_extraction = 0x0000000002000000, + effcat_safe_spot = 0x0000000004000000, + effcat_teleport_effects = 0x0000000008000000, + effcat_blob_emission_modulator = 0x0000000010000000, + effcat_detection_stealth = 0x0000000020000000, + effcat_target_paint = 0x0000000040000000, + effcat_highway = 0x0000000080000000, + effcat_eccm = 0x0000000100000000, + effcat_eccmcureable_effects = 0x0000000200000000, + effcat_zero_core_drop = 0x0000000400000000, + effcat_intrusion_effect = 0x0000000800000000, + effcat_pbs_booster_effects = 0x0000001000000000, + effcat_pbs_gap_generator_effect = 0x0000002000000000, + effcat_pbs_mining_tower_effect = 0x0000004000000000, + effcat_consumable_effects = 0x0000008000000000, + effcat_pbs_sensors_aura = 0x0000010000000000, + effcat_pbs_engineering_aura = 0x0000020000000000, + effcat_pbs_industry_aura = 0x0000040000000000, + effcat_terrain_object_effects = 0x0000080000000000, + effcat_zone_effects = 0x0000100000000000, // OPP: New Zone effects + effcat_environmental_effects = 0x0000200000000000, // OPP: New Zone effects + effcat_mine_detection = 0x0000400000000000, // OPP: Mine detection + } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 6e82851d0..54e22e25b 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -5,102 +5,123 @@ namespace Perpetuum.ExportedTypes { - public enum EffectType - { - undefined = 0, - effect_aggressor = 24, - effect_armor_hardener = 23, - effect_aura_gang_armor_max = 18, - effect_aura_gang_core_recharge_time = 35, - effect_aura_gang_core_usage_gathering = 21, - effect_aura_gang_critical_hit_chance = 34, - effect_aura_gang_ewar_optimal = 29, - effect_aura_gang_fast_extraction = 36, - effect_aura_gang_locking_range = 19, - effect_aura_gang_locking_time = 30, - effect_aura_gang_repaired_amount = 33, - effect_aura_gang_shield_absorbtion_ratio = 32, - effect_aura_gang_siege = 22, - effect_aura_gang_signature_radius = 31, - effect_aura_gang_speed = 20, - effect_blob_emission_modulator = 43, - effect_chemical_resist = 11, - effect_consumable_speed_max = 92, - effect_demobilizer = 2, - effect_despawn_timer = 39, - effect_detection = 44, - effect_eccm = 48, - effect_eccm_lockdrop = 49, - effect_explosive_resist = 14, - effect_extended_range = 17, - effect_highway = 47, - effect_intrusion_core_lvl1 = 53, - effect_intrusion_core_lvl2 = 61, - effect_intrusion_core_lvl3 = 68, - effect_intrusion_detection_lvl1 = 50, - effect_intrusion_detection_lvl2 = 58, - effect_intrusion_detection_lvl3 = 65, - effect_intrusion_engineering_lvl4_combined = 74, - effect_intrusion_geoscan_lvl1 = 57, - effect_intrusion_geoscan_lvl2 = 64, - effect_intrusion_geoscan_lvl3 = 71, - effect_intrusion_harvester_lvl1 = 55, - effect_intrusion_harvester_lvl2 = 62, - effect_intrusion_harvester_lvl3 = 69, - effect_intrusion_industrial_lvl4_combined = 73, - effect_intrusion_masking_lvl1 = 51, - effect_intrusion_masking_lvl2 = 59, - effect_intrusion_masking_lvl3 = 66, - effect_intrusion_mining_lvl1 = 52, - effect_intrusion_mining_lvl2 = 60, - effect_intrusion_mining_lvl3 = 67, - effect_intrusion_repair_lvl1 = 56, - effect_intrusion_repair_lvl2 = 63, - effect_intrusion_repair_lvl3 = 70, - effect_intrusion_signals_lvl4_combined = 72, - effect_invulnerable = 26, - effect_kinetic_resist = 12, - effect_pbs_booster_cycle_time_lvl1 = 78, - effect_pbs_booster_cycle_time_lvl2 = 81, - effect_pbs_booster_cycle_time_lvl3 = 84, - effect_pbs_booster_resist_lvl1 = 76, - effect_pbs_booster_resist_lvl2 = 79, - effect_pbs_booster_resist_lvl3 = 82, - effect_pbs_booster_sensor_lvl1 = 77, - effect_pbs_booster_sensor_lvl2 = 80, - effect_pbs_booster_sensor_lvl3 = 83, - effect_pbs_engineering_lvl1 = 94, - effect_pbs_engineering_lvl2 = 97, - effect_pbs_engineering_lvl3 = 100, - effect_pbs_gap_generator_masking_lvl1 = 85, - effect_pbs_gap_generator_masking_lvl2 = 86, - effect_pbs_gap_generator_masking_lvl3 = 87, - effect_pbs_industry_lvl1 = 95, - effect_pbs_industry_lvl2 = 98, - effect_pbs_industry_lvl3 = 101, - effect_pbs_mining_tower_gammaterial_lvl1 = 88, - effect_pbs_mining_tower_gammaterial_lvl2 = 89, - effect_pbs_mining_tower_gammaterial_lvl3 = 90, - effect_pbs_reinforce = 91, - effect_pbs_sensors_lvl1 = 93, - effect_pbs_sensors_lvl2 = 96, - effect_pbs_sensors_lvl3 = 99, - effect_pvp = 27, - effect_remote_sensor_boost = 4, - effect_safe_spot = 38, - effect_sensor_boost = 3, - effect_sensor_supress = 5, - effect_shield = 16, - effect_shield_hardener = 15, - effect_speed_booster = 25, - effect_stealth = 45, - effect_swiss_army_knife = 75, - effect_syndicate_area = 37, - effect_tag = 28, - effect_target_painting = 46, - effect_teleport_cooldown = 40, - effect_teleport_self_enabler = 102, - effect_teleport_sickness = 42, - effect_thermal_resist = 13 - } + public enum EffectType + { + undefined = 0, + effect_aggressor = 24, + effect_armor_hardener = 23, + effect_aura_gang_armor_max = 18, + effect_aura_gang_core_recharge_time = 35, + effect_aura_gang_core_usage_gathering = 21, + effect_aura_gang_critical_hit_chance = 34, + effect_aura_gang_ewar_optimal = 29, + effect_aura_gang_fast_extraction = 36, + effect_aura_gang_locking_range = 19, + effect_aura_gang_locking_time = 30, + effect_aura_gang_repaired_amount = 33, + effect_aura_gang_shield_absorbtion_ratio = 32, + effect_aura_gang_siege = 22, + effect_aura_gang_signature_radius = 31, + effect_aura_gang_speed = 20, + effect_blob_emission_modulator = 43, + effect_chemical_resist = 11, + effect_consumable_speed_max = 92, + effect_demobilizer = 2, + effect_despawn_timer = 39, + effect_detection = 44, + effect_eccm = 48, + effect_eccm_lockdrop = 49, + effect_explosive_resist = 14, + effect_extended_range = 17, + effect_highway = 47, + effect_intrusion_core_lvl1 = 53, + effect_intrusion_core_lvl2 = 61, + effect_intrusion_core_lvl3 = 68, + effect_intrusion_detection_lvl1 = 50, + effect_intrusion_detection_lvl2 = 58, + effect_intrusion_detection_lvl3 = 65, + effect_intrusion_engineering_lvl4_combined = 74, + effect_intrusion_geoscan_lvl1 = 57, + effect_intrusion_geoscan_lvl2 = 64, + effect_intrusion_geoscan_lvl3 = 71, + effect_intrusion_harvester_lvl1 = 55, + effect_intrusion_harvester_lvl2 = 62, + effect_intrusion_harvester_lvl3 = 69, + effect_intrusion_industrial_lvl4_combined = 73, + effect_intrusion_masking_lvl1 = 51, + effect_intrusion_masking_lvl2 = 59, + effect_intrusion_masking_lvl3 = 66, + effect_intrusion_mining_lvl1 = 52, + effect_intrusion_mining_lvl2 = 60, + effect_intrusion_mining_lvl3 = 67, + effect_intrusion_repair_lvl1 = 56, + effect_intrusion_repair_lvl2 = 63, + effect_intrusion_repair_lvl3 = 70, + effect_intrusion_signals_lvl4_combined = 72, + effect_invulnerable = 26, + effect_kinetic_resist = 12, + effect_pbs_booster_cycle_time_lvl1 = 78, + effect_pbs_booster_cycle_time_lvl2 = 81, + effect_pbs_booster_cycle_time_lvl3 = 84, + effect_pbs_booster_resist_lvl1 = 76, + effect_pbs_booster_resist_lvl2 = 79, + effect_pbs_booster_resist_lvl3 = 82, + effect_pbs_booster_sensor_lvl1 = 77, + effect_pbs_booster_sensor_lvl2 = 80, + effect_pbs_booster_sensor_lvl3 = 83, + effect_pbs_engineering_lvl1 = 94, + effect_pbs_engineering_lvl2 = 97, + effect_pbs_engineering_lvl3 = 100, + effect_pbs_gap_generator_masking_lvl1 = 85, + effect_pbs_gap_generator_masking_lvl2 = 86, + effect_pbs_gap_generator_masking_lvl3 = 87, + effect_pbs_industry_lvl1 = 95, + effect_pbs_industry_lvl2 = 98, + effect_pbs_industry_lvl3 = 101, + effect_pbs_mining_tower_gammaterial_lvl1 = 88, + effect_pbs_mining_tower_gammaterial_lvl2 = 89, + effect_pbs_mining_tower_gammaterial_lvl3 = 90, + effect_pbs_reinforce = 91, + effect_pbs_sensors_lvl1 = 93, + effect_pbs_sensors_lvl2 = 96, + effect_pbs_sensors_lvl3 = 99, + effect_pvp = 27, + effect_remote_sensor_boost = 4, + effect_safe_spot = 38, + effect_sensor_boost = 3, + effect_sensor_supress = 5, + effect_shield = 16, + effect_shield_hardener = 15, + effect_speed_booster = 25, + effect_stealth = 45, + effect_swiss_army_knife = 75, + effect_syndicate_area = 37, + effect_tag = 28, + effect_target_painting = 46, + effect_teleport_cooldown = 40, + effect_teleport_self_enabler = 102, + effect_teleport_sickness = 42, + effect_thermal_resist = 13, + // New effects - OPP + effect_beta_bonus = 103, + effect_beta2_bonus = 104, + effect_alpha_bonus = 105, + effect_alpha2_bonus = 106, + effect_day = 107, + effect_day_clear = 108, + effect_day_overcast = 109, + effect_night = 110, + effect_night_clear = 111, + effect_night_overcast = 112, + effect_weather_good = 113, + effect_weather_bad = 114, + effect_stronghold_despawn_timer = 115, + effect_gamma_t0 = 116, + effect_gamma_t1 = 117, + effect_gamma_t2 = 118, + effect_gamma_t3 = 119, + effect_gamma_tc = 120, + effect_mine_detector = 121, + effect_tellesis = 122, + } } diff --git a/src/Perpetuum.ExportedTypes/ExtensionNames.cs b/src/Perpetuum.ExportedTypes/ExtensionNames.cs index aad878cd2..046737bec 100644 --- a/src/Perpetuum.ExportedTypes/ExtensionNames.cs +++ b/src/Perpetuum.ExportedTypes/ExtensionNames.cs @@ -75,6 +75,8 @@ public static class ExtensionNames public const string EPRITON_MINING_BUFFER_EXPERT = "ext_epriton_mining_buffer_expert"; public const string EXPLOSION_RADIUS_MODIFIER = "ext_explosion_radius_modifier"; public const string FAST_EXTRACTION_LEADERSHIP = "ext_fast_extraction_leadership"; + public const string FLUX_MINER_EXPERT = "ext_flux_miner_expert"; + public const string FLUX_MINING_BUFFER_EXPERT = "ext_flux_mining_buffer_expert"; public const string FUEL_EFFICIENCY = "ext_fuel_efficiency"; public const string GAMMATERIAL_MINER_EXPERT = "ext_gammaterial_miner_expert"; public const string GAMMATERIAL_MINING_BUFFER_EXPERT = "ext_gammaterial_mining_buffer_expert"; diff --git a/src/Perpetuum.RequestHandlers/AccountConfirmEmail.cs b/src/Perpetuum.RequestHandlers/AccountConfirmEmail.cs new file mode 100644 index 000000000..d2d3836c7 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/AccountConfirmEmail.cs @@ -0,0 +1,31 @@ +using Perpetuum.Accounting; +using Perpetuum.Host.Requests; + + +namespace Perpetuum.RequestHandlers +{ + public class AccountConfirmEmail : IRequestHandler + { + + private readonly IAccountRepository _accountRepository; + + public AccountConfirmEmail(IAccountRepository accountRepository) + { + _accountRepository = accountRepository; + } + + public void HandleRequest(IRequest request) + { + var accountId = request.Data.GetOrDefault(k.accountID); + var account = _accountRepository.Get(accountId); + if (account == null) + { + throw new PerpetuumException(ErrorCodes.NoSuchUser); + } + + account.ForceConfirmEmail(); + Message.Builder.FromRequest(request).WithOk().Send(); + + } + } +} diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs index ff1ab7d2e..790f0b15d 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs @@ -1,5 +1,7 @@ using Perpetuum.Accounting; +using Perpetuum.Data; using Perpetuum.Host.Requests; +using System.Data; namespace Perpetuum.RequestHandlers.AdminTools { @@ -25,7 +27,8 @@ public void HandleRequest(IRequest request) CampaignId = "{\"host\":\"tooladmin\"}" }; - if (_accountRepository.Get(account.Email,account.Password) != null) + //If email exists - throw error + if (_accountRepository.Get(account.Email, account.Password) != null) { Message.Builder.FromRequest(request).WithError(ErrorCodes.AccountAlreadyExists).Send(); return; diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs index 1f25bf9e9..3d4e10016 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs @@ -25,6 +25,23 @@ public void HandleRequest(IRequest request) if (!si.IsOpen) throw new PerpetuumException(ErrorCodes.InviteOnlyServer); + // if an account was already created using this session, reject this creation attempt. + if (request.Session.AccountCreatedInSession) + { + throw new PerpetuumException(ErrorCodes.MaxIterationsExceeded); + } + + //If email is not a well-formed email, reject this creation attempt + try + { + var addr = new System.Net.Mail.MailAddress(email); + } + catch + { + //TODO: Client Fixme - Client needs good error code for bad-email input + throw new PerpetuumException(ErrorCodes.InvalidAttribute); + } + var account = new Account { Email = email, @@ -33,7 +50,8 @@ public void HandleRequest(IRequest request) CampaignId = "{\"host\":\"opencreate\"}" }; - if (_accountRepository.Get(account.Email,account.Password) != null) + //If email exists - throw error + if (_accountRepository.Get(account.Email, account.Password) != null) { Message.Builder.FromRequest(request).WithError(ErrorCodes.AccountAlreadyExists).Send(); return; @@ -41,6 +59,9 @@ public void HandleRequest(IRequest request) _accountRepository.Insert(account); + // if we get this far, make sure we can't sit here and make accounts. + request.Session.AccountCreatedInSession = true; + Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } diff --git a/src/Perpetuum.RequestHandlers/AdminTools/CharactersOnline.cs b/src/Perpetuum.RequestHandlers/AdminTools/CharactersOnline.cs new file mode 100644 index 000000000..98903d3fe --- /dev/null +++ b/src/Perpetuum.RequestHandlers/AdminTools/CharactersOnline.cs @@ -0,0 +1,48 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Sessions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.RequestHandlers.AdminTools +{ + public class CharactersOnline : IRequestHandler + { + + private readonly ISessionManager _sessionManager; + + public CharactersOnline(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + public void HandleRequest(IRequest request) + { + var sessions = _sessionManager.Sessions; + + var x = sessions.ToDictionary("s", s => + { + var d = new Dictionary + { + [k.accessLevel] = (int)s.AccessLevel, + [k.accountID] = s.AccountId, + [k.characterID] = (s.Character == Character.None) ? 0 : s.Character.Id, + [k.nick] = (s.Character == Character.None) ? "No Character" : s.Character.Nick, + [k.zoneID] = (s.Character == Character.None) ? 0 : s.Character.ZoneId, + [k.docked] = (s.Character == Character.None) ? false : s.Character.IsDocked, + [k.name] = (s.Character.GetCurrentDockingBase() is null) ? "Unknown" : s.Character.GetCurrentDockingBase().Name, + [k.position] = (s.Character.GetPlayerRobotFromZone() != null) ? s.Character.GetPlayerRobotFromZone().CurrentPosition : new Position(), + [k.steambuildid] = s.SteamBuild, + [k.clientver] = s.ClientVersion, + [k.ip] = s.RemoteEndPoint.Address.ToString() + }; + return d; + }); + + Message.Builder.FromRequest(request).WithData(x).Send(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Channels/ChannelGlobalMute.cs b/src/Perpetuum.RequestHandlers/Channels/ChannelGlobalMute.cs index 2d8366de3..2a8d5f250 100644 --- a/src/Perpetuum.RequestHandlers/Channels/ChannelGlobalMute.cs +++ b/src/Perpetuum.RequestHandlers/Channels/ChannelGlobalMute.cs @@ -13,6 +13,7 @@ public void HandleRequest(IRequest request) var character = Character.Get(request.Data.GetOrDefault(k.characterID)); var state = request.Data.GetOrDefault(k.state).ToBool(); character.GlobalMuted = state; + Message.Builder.FromRequest(request).WithOk().Send(); scope.Complete(); } } diff --git a/src/Perpetuum.RequestHandlers/Channels/ChannelListAll.cs b/src/Perpetuum.RequestHandlers/Channels/ChannelListAll.cs new file mode 100644 index 000000000..4d751c326 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Channels/ChannelListAll.cs @@ -0,0 +1,22 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Services.Channels; + +namespace Perpetuum.RequestHandlers.Channels +{ + public class ChannelListAll : IRequestHandler + { + private readonly IChannelManager _channelManager; + + public ChannelListAll(IChannelManager channelManager) + { + _channelManager = channelManager; + } + + public void HandleRequest(IRequest request) + { + var character = request.Session.Character; + var result = _channelManager.GetAllChannels().ToDictionary("c", c => c.ToDictionary(character, false)); + Message.Builder.FromRequest(request).WithData(result).WithEmpty().Send(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Channels/ChannelTalk.cs b/src/Perpetuum.RequestHandlers/Channels/ChannelTalk.cs index 332a2cb7c..29c832752 100644 --- a/src/Perpetuum.RequestHandlers/Channels/ChannelTalk.cs +++ b/src/Perpetuum.RequestHandlers/Channels/ChannelTalk.cs @@ -18,7 +18,7 @@ public void HandleRequest(IRequest request) var message = request.Data.GetOrDefault(k.message); var character = request.Session.Character; - _channelManager.Talk(channelName, character, message); + _channelManager.Talk(channelName, character, message, request); Message.Builder.FromRequest(request).WithOk().Send(); } } diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs index ef6709927..b9ae3cf05 100644 --- a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs +++ b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using System.Transactions; using Perpetuum.Accounting; using Perpetuum.Accounting.Characters; @@ -11,6 +12,7 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Host.Requests; using Perpetuum.Services.Channels; +using Perpetuum.Services.Mail; using Perpetuum.Services.Sparks; using Perpetuum.Units.DockingBases; using Perpetuum.Zones.Training; @@ -19,6 +21,10 @@ namespace Perpetuum.RequestHandlers.Characters { public class CharacterCreate : IRequestHandler { + private readonly int ACCOUNT_START_EP; + private TimeSpan WAIT_TIME_BEFORE_SENDING_MAIL = TimeSpan.FromSeconds(10); + private TimeSpan WAIT_TIME_BEFORE_SENDING_WELCOME_MESSAGE = TimeSpan.FromSeconds(10); + private readonly IAccountManager _accountManager; private readonly IChannelManager _channelManager; private readonly DockingBaseHelper _dockingBaseHelper; @@ -26,7 +32,7 @@ public class CharacterCreate : IRequestHandler private readonly IEntityServices _entityServices; private readonly SparkHelper _sparkHelper; - public CharacterCreate(IAccountManager accountManager,IChannelManager channelManager,DockingBaseHelper dockingBaseHelper,CharacterFactory characterFactory,IEntityServices entityServices,SparkHelper sparkHelper) + public CharacterCreate(IAccountManager accountManager,IChannelManager channelManager,DockingBaseHelper dockingBaseHelper,CharacterFactory characterFactory,IEntityServices entityServices,SparkHelper sparkHelper,GlobalConfiguration globalConfiguration) { _accountManager = accountManager; _channelManager = channelManager; @@ -34,6 +40,8 @@ public CharacterCreate(IAccountManager accountManager,IChannelManager channelMan _characterFactory = characterFactory; _entityServices = entityServices; _sparkHelper = sparkHelper; + // Using EP for new player from globalConfiguration. + ACCOUNT_START_EP = globalConfiguration.StartEP; } public void HandleRequest(IRequest request) @@ -61,6 +69,7 @@ public void HandleRequest(IRequest request) if (account.FirstCharacterDate == null) { account.FirstCharacterDate = DateTime.Now; + _accountManager.AddExtensionPoints(account, ACCOUNT_START_EP); _accountManager.Repository.Update(account); } @@ -99,9 +108,17 @@ public void HandleRequest(IRequest request) // training dockingBase = _dockingBaseHelper.GetTrainingDockingBase(); corporation = ((TrainingDockingBase)dockingBase).GetTrainingCorporation(); - character.SetAllExtensionLevel(5); + character.SetAllExtensionLevel(6); dockingBase.CreateStarterRobotForCharacter(character); character.AddToWallet(TransactionType.CharacterCreate,10000000); + + Task.Delay(WAIT_TIME_BEFORE_SENDING_MAIL) + .ContinueWith(task => MailHandler.SendWelcomeMailBeginTutorial(character)); + Task.Delay(WAIT_TIME_BEFORE_SENDING_WELCOME_MESSAGE) + .ContinueWith(task => + { + ChannelMessageHandler.SendNewPlayerTutorialMessage(_channelManager, character.Nick); + }); } character.CurrentDockingBaseEid = dockingBase.Eid; diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterSelect.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterSelect.cs index a2e1e744a..48e36298a 100644 --- a/src/Perpetuum.RequestHandlers/Characters/CharacterSelect.cs +++ b/src/Perpetuum.RequestHandlers/Characters/CharacterSelect.cs @@ -45,7 +45,7 @@ public void HandleRequest(IRequest request) { character.ZoneId = null; character.ZonePosition = null; - character.GetCurrentDockingBase()?.JoinChannel(character); + character.GetCurrentDockingBase()?.TryJoinChannel(character); } var corporation = character.GetCorporation(); diff --git a/src/Perpetuum.RequestHandlers/Chat.cs b/src/Perpetuum.RequestHandlers/Chat.cs index f23b4c6c2..6c4fcb5b7 100644 --- a/src/Perpetuum.RequestHandlers/Chat.cs +++ b/src/Perpetuum.RequestHandlers/Chat.cs @@ -59,7 +59,7 @@ private ILogger GetChatLogger(Character sender, Character target) ObjectHelper.Swap(ref x, ref y); } - var hash = (ulong) x << 32 | (ulong) y; + var hash = (ulong) x << 32 | (uint) y; var senderNick = sender.Nick; diff --git a/src/Perpetuum.RequestHandlers/Corporations/CorporationVolunteerForCeo.cs b/src/Perpetuum.RequestHandlers/Corporations/CorporationVolunteerForCeo.cs index a307e0e40..0060cf884 100644 --- a/src/Perpetuum.RequestHandlers/Corporations/CorporationVolunteerForCeo.cs +++ b/src/Perpetuum.RequestHandlers/Corporations/CorporationVolunteerForCeo.cs @@ -22,7 +22,7 @@ public void HandleRequest(IRequest request) var corporation = character.GetPrivateCorporationOrThrow(); var role = corporation.GetMemberRole(character); - if (role.IsAnyRole(CorporationRole.CEO,CorporationRole.DeputyCEO)) + if (!role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO)) throw new PerpetuumException(ErrorCodes.InsufficientPrivileges); var volunteerCEO = _volunteerCEOService.GetVolunteer(corporation.Eid); diff --git a/src/Perpetuum.RequestHandlers/CreateItemRequestHandler.cs b/src/Perpetuum.RequestHandlers/CreateItemRequestHandler.cs index 3eeddf161..d71ce3122 100644 --- a/src/Perpetuum.RequestHandlers/CreateItemRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/CreateItemRequestHandler.cs @@ -23,6 +23,7 @@ public void HandleRequest(IRequest request) { var character = request.Session.Character; var targetContainer = GetTargetContainer(request); + character.IsInTraining().ThrowIfEqual(false, ErrorCodes.AccessDenied); var item = CreateItem(request).ThrowIfNull(ErrorCodes.WTFErrorMedicalAttentionSuggested); item.Owner = character.Eid; @@ -51,7 +52,7 @@ private static Container GetTargetContainer(IRequest request) } [CanBeNull] - private Item CreateItem(IRequest request) + public Item CreateItem(IRequest request) { var definition = request.Data.GetOrDefault(k.definition); if (definition > 0) diff --git a/src/Perpetuum.RequestHandlers/EPBonusEvent.cs b/src/Perpetuum.RequestHandlers/EPBonusEvent.cs new file mode 100644 index 000000000..56fc64e43 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/EPBonusEvent.cs @@ -0,0 +1,38 @@ +using System; +using Perpetuum.Accounting; +using Perpetuum.Data; +using Perpetuum.Host.Requests; +using Perpetuum.Services.EventServices; + +namespace Perpetuum.RequestHandlers.Extensions +{ + public class EPBonusEvent : IRequestHandler + { + private readonly EPBonusEventService _eventService; + private TimeSpan MAX_DURATION = TimeSpan.FromDays(14); + private const int MIN_BONUS = 0; + private const int MAX_BONUS = 25; + + public EPBonusEvent(EPBonusEventService eventService) + { + _eventService = eventService; + } + + public void HandleRequest(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var bonusAmount = request.Data.GetOrDefault(k.bonus); + var durationHours = request.Data.GetOrDefault(k.duration); + + var checkArgs = bonusAmount >= MIN_BONUS && bonusAmount <= MAX_BONUS; + checkArgs = checkArgs && durationHours <= MAX_DURATION.TotalHours; + checkArgs.ThrowIfFalse(ErrorCodes.InputTooHigh); + + _eventService.SetEvent(bonusAmount, TimeSpan.FromHours(durationHours)); + + scope.Complete(); + } + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Extensions/ExtensionBuyEpBoost.cs b/src/Perpetuum.RequestHandlers/Extensions/ExtensionBuyEpBoost.cs index 11d50f5d3..33f416c77 100644 --- a/src/Perpetuum.RequestHandlers/Extensions/ExtensionBuyEpBoost.cs +++ b/src/Perpetuum.RequestHandlers/Extensions/ExtensionBuyEpBoost.cs @@ -23,6 +23,9 @@ public ExtensionBuyEpBoost(IAccountManager accountManager,IAccountRepository acc public void HandleRequest(IRequest request) { + // Disable this command completely so as to not cause confusion with the in-game item + false.ThrowIfFalse(ErrorCodes.NoSuchCommand); + /* using (var scope = Db.CreateTransaction()) { var account = _accountManager.Repository.Get(request.Session.AccountId).ThrowIfNull(ErrorCodes.AccountNotFound); @@ -42,7 +45,7 @@ public void HandleRequest(IRequest request) var startTime = DateTime.Now; var endTime = DateTime.Now.AddDays(EP_BOOST_PERIOD_LENGTH_IN_DAYS); - _accountManager.ExtensionSubscriptionStart(account,startTime, endTime); + _accountManager.ExtensionSubscriptionStart(account, startTime, endTime, 2); account.ValidUntil = endTime; } else @@ -72,7 +75,8 @@ public void HandleRequest(IRequest request) }); scope.Complete(); - } + + }*/ } } } diff --git a/src/Perpetuum.RequestHandlers/Extensions/ExtensionFreeAllLockedEpByCommand.cs b/src/Perpetuum.RequestHandlers/Extensions/ExtensionFreeAllLockedEpByCommand.cs new file mode 100644 index 000000000..0b5920011 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Extensions/ExtensionFreeAllLockedEpByCommand.cs @@ -0,0 +1,34 @@ +using System; +using Perpetuum.Accounting; +using Perpetuum.Data; +using Perpetuum.Host.Requests; + +namespace Perpetuum.RequestHandlers.Extensions +{ + public class ExtensionFreeAllLockedEpByCommand : IRequestHandler + { + private readonly IAccountManager _accountManager; + private readonly IAccountRepository _accountRepository; + + public ExtensionFreeAllLockedEpByCommand(IAccountManager accountManager, IAccountRepository accountRepository) + { + _accountManager = accountManager; + _accountRepository = accountRepository; + } + + public void HandleRequest(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var accountID = request.Data.GetOrDefault(k.accountID); + var account = _accountManager.Repository.Get(accountID).ThrowIfNull(ErrorCodes.AccountNotFound); + + //Free all locked EP on account + var currentlockedEp = _accountManager.GetLockedEpByAccount(account); + _accountManager.FreeLockedEp(account, currentlockedEp); + _accountRepository.Update(account); + scope.Complete(); + } + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs b/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs index a1e6edc7e..967ae2880 100644 --- a/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs +++ b/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs @@ -6,33 +6,27 @@ namespace Perpetuum.RequestHandlers.Extensions { - - /// /// Reset character extension for account credit ... not used yet /// public class ExtensionResetCharacter : IRequestHandler { - private readonly IAccountManager _accountManager; - private readonly IAccountRepository _accountRepository; - private readonly MtProductHelper _mtProductHelper; + private readonly IAccountManager accountManager; - public ExtensionResetCharacter(IAccountManager accountManager,IAccountRepository accountRepository,MtProductHelper mtProductHelper) + public ExtensionResetCharacter(IAccountManager accountManager) { - _accountManager = accountManager; - _accountRepository = accountRepository; - _mtProductHelper = mtProductHelper; + this.accountManager = accountManager; } public void HandleRequest(IRequest request) { using (var scope = Db.CreateTransaction()) { - var account = _accountManager.Repository.Get(request.Session.AccountId).ThrowIfNull(ErrorCodes.AccountNotFound); + var account = accountManager.Repository.Get(request.Session.AccountId).ThrowIfNull(ErrorCodes.AccountNotFound); var character = Character.Get(request.Data.GetOrDefault(k.characterID)); - - if ( character == Character.None ) + + if (character == Character.None) throw new PerpetuumException(ErrorCodes.CharacterNotFound); //only characters that belong to the issuers account @@ -43,57 +37,30 @@ public void HandleRequest(IRequest request) (character.IsDocked).ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); - var product = _mtProductHelper.GetByAccountTransactionType(AccountTransactionType.ExtensionReset); - var wallet = _accountManager.GetWallet(account,AccountTransactionType.ExtensionReset); - - wallet.Balance -= product.price; - - var e = new AccountTransactionLogEvent(account,AccountTransactionType.ExtensionReset) - { - Credit = wallet.Balance, - CreditChange = -product.price - }; - - _accountManager.LogTransaction(e); - - _accountRepository.Update(account); - //current extensions var extensionCollection = character.GetExtensions(); //default extensions var defaultExtensionHandler = new CharacterDefaultExtensionHelper(character); - + foreach (var extension in extensionCollection) { //returns 0 if the extension is not starter extension //returns the minimum level if the extension is starter int newLevel; - defaultExtensionHandler.IsStartingExtension( extension, out newLevel); - + defaultExtensionHandler.IsStartingExtension(extension, out newLevel); + var resultExtension = new Extension(extension.id, newLevel); - + character.SetExtension(resultExtension); } character.DeleteAllSpentPoints(); - //ezt nem csinalja, csak nyersen reseteli az extensionoket - /* - ExtensionHelper.DeleteAllNonForeverPenaltyPoints(character); - */ - - // %%% itt hagytuk abba - Message.Builder.FromRequest(request).WithOk().Send(); - + scope.Complete(); } - } - - - - } -} +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/FittingPreset/FittingPresetApply.cs b/src/Perpetuum.RequestHandlers/FittingPreset/FittingPresetApply.cs index 41c483fcf..3811a9c3d 100644 --- a/src/Perpetuum.RequestHandlers/FittingPreset/FittingPresetApply.cs +++ b/src/Perpetuum.RequestHandlers/FittingPreset/FittingPresetApply.cs @@ -39,6 +39,7 @@ public override void HandleRequest(IRequest request) var container = Container.GetWithItems(containerEid, character); robot.EmptyRobot(character, container, false); + robot.Initialize(character); foreach (var moduleInfos in preset.Modules.GroupBy(i => i.Component)) { diff --git a/src/Perpetuum.RequestHandlers/ForceDock.cs b/src/Perpetuum.RequestHandlers/ForceDock.cs index c94edfc31..6530e2773 100644 --- a/src/Perpetuum.RequestHandlers/ForceDock.cs +++ b/src/Perpetuum.RequestHandlers/ForceDock.cs @@ -1,6 +1,8 @@ using Perpetuum.Data; using Perpetuum.Host.Requests; using Perpetuum.Log; +using Perpetuum.Services.Sessions; +using Perpetuum.Units.DockingBases; namespace Perpetuum.RequestHandlers { @@ -26,4 +28,35 @@ public void HandleRequest(IRequest request) } } } + + public class ForceDockAdmin : IRequestHandler + { + private readonly ISessionManager _sessionManager; + private readonly DockingBaseHelper _dockingBaseHelper; + + public ForceDockAdmin(ISessionManager sessionManager, DockingBaseHelper dockingBaseHelper) + { + _sessionManager = sessionManager; + _dockingBaseHelper = dockingBaseHelper; + } + + public void HandleRequest(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var id = request.Data.GetOrDefault(k.characterID); + var characterSession = _sessionManager.GetByCharacter(id); + var Zone = characterSession.Character.GetCurrentZone(); + + var baseEid = 561; // TMA. + var dockingbase = _dockingBaseHelper.GetDockingBase(baseEid); + var player = Zone.GetPlayer(characterSession.Character.GetActiveRobot().Eid); + player.DockToBase(Zone, dockingbase); + + Message.Builder.ToClient(characterSession).WithError(ErrorCodes.YouAreHappyNow).Send(); + + scope.Complete(); + } + } + } } \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/GetCommands.cs b/src/Perpetuum.RequestHandlers/GetCommands.cs new file mode 100644 index 000000000..bbde33665 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/GetCommands.cs @@ -0,0 +1,30 @@ +using Perpetuum.Host.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.RequestHandlers +{ + public class GetCommands : IRequestHandler + { + public void HandleRequest(IRequest request) + { + var commands = typeof(Commands).GetFields(BindingFlags.Static | BindingFlags.Public).Select(info => (Command)info.GetValue(null)); + + var x = commands.ToDictionary("c", r => + { + var d = new Dictionary + { + [k.command] = (string)r.Text, + [k.accessLevel] = (int)r.AccessLevel + }; + return d; + }); + + Message.Builder.FromRequest(request).WithData(x).Send(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/GetHighScores.cs b/src/Perpetuum.RequestHandlers/GetHighScores.cs index 93fc6e8e1..1e24b28e4 100644 --- a/src/Perpetuum.RequestHandlers/GetHighScores.cs +++ b/src/Perpetuum.RequestHandlers/GetHighScores.cs @@ -6,7 +6,7 @@ namespace Perpetuum.RequestHandlers { public class GetHighScores : IRequestHandler { - private static readonly DateTimeRange _timeRange = DateTime.Now.ToRange(-TimeSpan.FromDays(30)); + private readonly DateTimeRange _timeRange = DateTime.Now.ToRange(-TimeSpan.FromDays(30)); private readonly IHighScoreService _highScoreService; public GetHighScores(IHighScoreService highScoreService) diff --git a/src/Perpetuum.RequestHandlers/GetNews.cs b/src/Perpetuum.RequestHandlers/GetNews.cs index e15735103..24f6eb38f 100644 --- a/src/Perpetuum.RequestHandlers/GetNews.cs +++ b/src/Perpetuum.RequestHandlers/GetNews.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; using Perpetuum.Data; using Perpetuum.Host.Requests; @@ -11,8 +12,19 @@ public void HandleRequest(IRequest request) { var newsAmount = request.Data.GetOrDefault(k.amount); var newsLanguage = request.Data.GetOrDefault(k.language); + var all = request.Data.GetOrDefault(k.all).ToBool(); - var result = Db.Query().CommandText("select top(@amount) title,body,ntime,type,idx,language from news where language=@language or language=0 order by idx desc") + StringBuilder stringBuilder = new StringBuilder("select top(@amount) title, body, ntime, type, idx, language from news "); + if (all) + { + stringBuilder.Append("order by idx desc"); + } + else + { + stringBuilder.Append("where language = @language or language = 0 order by idx desc"); + } + + var result = Db.Query().CommandText(stringBuilder.ToString()) .SetParameter("@amount", newsAmount) .SetParameter("@language", newsLanguage) .Execute() diff --git a/src/Perpetuum.RequestHandlers/GetRobotInfo.cs b/src/Perpetuum.RequestHandlers/GetRobotInfo.cs index 9fca34eb9..77dfc3360 100644 --- a/src/Perpetuum.RequestHandlers/GetRobotInfo.cs +++ b/src/Perpetuum.RequestHandlers/GetRobotInfo.cs @@ -24,55 +24,57 @@ public GetRobotInfo(IZoneManager zoneManager,RobotHelper robotHelper) public void HandleRequest(IRequest request) { - TransactionScope scope = null; - try + // make sure the transaction scope is disposed of properly. + using (TransactionScope scope = Db.CreateTransaction()) { - if (TryGetRobotFromZone(request, out Robot robot)) + try { - scope = Db.CreateTransaction(); - robot.EnlistTransaction(); - } - else - { - var robotEid = request.Data.GetOrDefault(k.robotEID); - robot = _robotHelper.LoadRobotForCharacter(robotEid,request.Session.Character); - } + if (TryGetRobotFromZone(request, out Robot robot)) + { + robot.EnlistTransaction(); + } + else + { + var robotEid = request.Data.GetOrDefault(k.robotEID); + robot = _robotHelper.LoadRobotForCharacter(robotEid, request.Session.Character); + } - if (robot == null) - throw new PerpetuumException(ErrorCodes.RobotNotFound); + if (robot == null) + throw new PerpetuumException(ErrorCodes.RobotNotFound); - if ( !robot.IsSingleAndUnpacked) - throw new PerpetuumException(ErrorCodes.RobotMustbeSingleAndNonRepacked); + if (!robot.IsSingleAndUnpacked) + throw new PerpetuumException(ErrorCodes.RobotMustbeSingleAndNonRepacked); - if (ForFitting) - { - robot.CheckOwnerOnlyCharacterAndThrowIfFailed(request.Session.Character); - } - else - { - robot.CheckOwnerCharacterAndCorporationAndThrowIfFailed(request.Session.Character); - } + if (ForFitting) + { + robot.CheckOwnerOnlyCharacterAndThrowIfFailed(request.Session.Character); + } + else + { + robot.CheckOwnerCharacterAndCorporationAndThrowIfFailed(request.Session.Character); + } - switch (robot.GetOrLoadParentEntity()) - { - case DefaultSystemContainer _: - case RobotInventory _ when ForFitting: - case CorporateHangar _ when ForFitting: + switch (robot.GetOrLoadParentEntity()) { - throw new PerpetuumException(ErrorCodes.AccessDenied); + case DefaultSystemContainer _: + case RobotInventory _ when ForFitting: + case CorporateHangar _ when ForFitting: + { + throw new PerpetuumException(ErrorCodes.AccessDenied); + } } - } - var result = new Dictionary + var result = new Dictionary { { k.robot,robot.ToDictionary() } }; - Message.Builder.FromRequest(request).WithData(result).WrapToResult().WithEmpty().Send(); - } - finally - { - scope?.Complete(); + Message.Builder.FromRequest(request).WithData(result).WrapToResult().WithEmpty().Send(); + } + finally + { + scope?.Complete(); + } } } diff --git a/src/Perpetuum.RequestHandlers/GiftOpen.cs b/src/Perpetuum.RequestHandlers/GiftOpen.cs index 8ebc38574..3f9136885 100644 --- a/src/Perpetuum.RequestHandlers/GiftOpen.cs +++ b/src/Perpetuum.RequestHandlers/GiftOpen.cs @@ -20,14 +20,15 @@ public void HandleRequest(IRequest request) { using (var scope = Db.CreateTransaction()) { - var giftEid = request.Data.GetOrDefault(k.eid); + var itemEid = request.Data.GetOrDefault(k.eid); + var item = this._entityServices.Repository.Load(itemEid); var character = request.Session.Character; character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); var publicContainer = character.GetPublicContainerWithItems(); - var giftItem = (Gift)publicContainer.GetItemOrThrow(giftEid,true).Unstack(1); - var randomItem = giftItem.Open(publicContainer,character); + var giftItem = (Gift)publicContainer.GetItemOrThrow(itemEid, true).Unstack(1); + var randomItem = giftItem.Open(publicContainer, character); _entityServices.Repository.Delete(giftItem); publicContainer.Save(); @@ -48,7 +49,7 @@ public void HandleRequest(IRequest request) }; Transaction.Current.OnCommited(() => Message.Builder.FromRequest(request).WithData(result).Send()); - + scope.Complete(); } } diff --git a/src/Perpetuum.RequestHandlers/Markets/MarketItemsInRange.cs b/src/Perpetuum.RequestHandlers/Markets/MarketItemsInRange.cs index 6a4e1af3a..8d827488d 100644 --- a/src/Perpetuum.RequestHandlers/Markets/MarketItemsInRange.cs +++ b/src/Perpetuum.RequestHandlers/Markets/MarketItemsInRange.cs @@ -47,13 +47,13 @@ public void HandleRequest(IRequest request) //exclude training market //include corp orders if character in private corp //include normal orders (non corp) - var orders = _marketOrderRepository.GetAllByDefinition(itemDefinition).Where(r => r.marketEID != trainingMarketEid && (r.forMembersOf == corporationEid || r.forMembersOf == null)).ToArray(); + //var orders = _marketOrderRepository.GetAllByDefinition(itemDefinition).Where(r => r.marketEID != trainingMarketEid && (r.forMembersOf == corporationEid || r.forMembersOf == null)).ToArray(); //ez szabalyozza le, hogy nem latszanak a pbs marketek //itt lehet ugyeskedni //filtered markets - //var orders = repo.GetByMarketEids(MarketHandler.GetAllDefaultMarketsEids(), itemDefinition, request.character.CorporationEid).ToArray(); + var orders = _marketOrderRepository.GetByMarketEids(_marketHandler.GetAllVisibleMarketsFor(character), itemDefinition, corporationEid).ToArray(); var orderDict = new Dictionary(orders.Length); var counter = 0; diff --git a/src/Perpetuum.RequestHandlers/Missions/MissionGetOptions.cs b/src/Perpetuum.RequestHandlers/Missions/MissionGetOptions.cs index 7c444ca12..1405266c0 100644 --- a/src/Perpetuum.RequestHandlers/Missions/MissionGetOptions.cs +++ b/src/Perpetuum.RequestHandlers/Missions/MissionGetOptions.cs @@ -1,6 +1,9 @@ -using Perpetuum.Host.Requests; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Host.Requests; using Perpetuum.Services.MissionEngine.MissionDataCacheObjects; using Perpetuum.Services.MissionEngine.MissionProcessorObjects; +using Perpetuum.Units.FieldTerminals; namespace Perpetuum.RequestHandlers.Missions { @@ -8,11 +11,13 @@ public class MissionGetOptions : IRequestHandler { private readonly MissionProcessor _missionProcessor; private readonly MissionDataCache _missionDataCache; + private readonly IEntityRepository _entityRepository; - public MissionGetOptions(MissionProcessor missionProcessor,MissionDataCache missionDataCache) + public MissionGetOptions(MissionProcessor missionProcessor, MissionDataCache missionDataCache, IEntityRepository entityRepository) { _missionProcessor = missionProcessor; _missionDataCache = missionDataCache; + _entityRepository = entityRepository; } public void HandleRequest(IRequest request) @@ -29,8 +34,13 @@ public void HandleRequest(IRequest request) { //use the optional parameter locationEid = request.Data.GetOrDefault(k.eid); + // Load Entity to see if it's a custom field terminal + var terminal = _entityRepository.Load(locationEid) + .ThrowIfNull(ErrorCodes.TargetNotFound) + .ThrowIfNotType(ErrorCodes.MissionNotAvailable); + // Don't show missions for rally terminals + terminal.ED.Name.ThrowIfEqual(DefinitionNames.FIELD_TERMINAL_RALLY, ErrorCodes.MissionNotAvailable); } - var location = _missionDataCache.GetLocationByEid(locationEid).ThrowIfNull(ErrorCodes.ItemNotFound); _missionProcessor.GetOptionsByRequest(character, location); } diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 42b2ab696..a0a729793 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -47,6 +47,7 @@ + @@ -59,6 +60,7 @@ + @@ -83,6 +85,7 @@ + @@ -122,6 +125,7 @@ + @@ -205,6 +209,10 @@ + + + + @@ -486,6 +494,7 @@ + @@ -525,6 +534,7 @@ + @@ -571,6 +581,7 @@ + @@ -581,6 +592,7 @@ + @@ -609,13 +621,16 @@ + + + diff --git a/src/Perpetuum.RequestHandlers/ProximityProbeGetRegistrationInfo.cs b/src/Perpetuum.RequestHandlers/ProximityProbeGetRegistrationInfo.cs index a9ab527d6..cbb86c7a6 100644 --- a/src/Perpetuum.RequestHandlers/ProximityProbeGetRegistrationInfo.cs +++ b/src/Perpetuum.RequestHandlers/ProximityProbeGetRegistrationInfo.cs @@ -18,7 +18,7 @@ public void HandleRequest(IRequest request) var probeEid = request.Data.GetOrDefault(k.eid); var character = request.Session.Character; - var probe = _unitHelper.GetUnitOrThrow(probeEid); + var probe = _unitHelper.GetUnitOrThrow(probeEid); probe.HasAccess(character).ThrowIfError(); var result = probe.GetProbeRegistrationInfo(); diff --git a/src/Perpetuum.RequestHandlers/ProximityProbeList.cs b/src/Perpetuum.RequestHandlers/ProximityProbeList.cs index 3d5ebaaa4..8747d9a07 100644 --- a/src/Perpetuum.RequestHandlers/ProximityProbeList.cs +++ b/src/Perpetuum.RequestHandlers/ProximityProbeList.cs @@ -21,10 +21,10 @@ public void HandleRequest(IRequest request) var corporation = character.GetPrivateCorporationOrThrow(); var role = corporation.GetMemberRole(character); - var allProbes = ProximityProbeBase.IsAllProbesVisible(role); - var probeEids = allProbes ? corporation.GetProximityProbeEids() : PBSRegisterHelper.PBSRegGetEidsByRegisteredCharacter(character); + var allProbes = ProximityDeviceBase.IsAllProbesVisible(role); + var probeEids = allProbes ? corporation.GetProximityProbeEids().Union(corporation.GetLandMineEids()) : PBSRegisterHelper.PBSRegGetEidsByRegisteredCharacter(character); - var probesDict = _zoneManager.Zones.GetUnits() + var probesDict = _zoneManager.Zones.GetUnits() .Where(probeBase => probeEids.Contains(probeBase.Eid)) .ToDictionary("c", p => p.ToDictionary()); diff --git a/src/Perpetuum.RequestHandlers/ProximityProbeRegisterSet.cs b/src/Perpetuum.RequestHandlers/ProximityProbeRegisterSet.cs index 0feef1e6a..424961f91 100644 --- a/src/Perpetuum.RequestHandlers/ProximityProbeRegisterSet.cs +++ b/src/Perpetuum.RequestHandlers/ProximityProbeRegisterSet.cs @@ -29,7 +29,7 @@ public void HandleRequest(IRequest request) playersToRegister.Length.ThrowIfEqual(0, ErrorCodes.WTFErrorMedicalAttentionSuggested); - var probe = _unitHelper.GetUnitOrThrow(probeEid); + var probe = _unitHelper.GetUnitOrThrow(probeEid); probe.HasAccess(character).ThrowIfError(); var corporation = character.GetPrivateCorporationOrThrow(); diff --git a/src/Perpetuum.RequestHandlers/ProximityProbeSetName.cs b/src/Perpetuum.RequestHandlers/ProximityProbeSetName.cs index 61db99fbf..7c7bb2edf 100644 --- a/src/Perpetuum.RequestHandlers/ProximityProbeSetName.cs +++ b/src/Perpetuum.RequestHandlers/ProximityProbeSetName.cs @@ -23,7 +23,7 @@ public void HandleRequest(IRequest request) var probeName = request.Data.GetOrDefault(k.name); var character = request.Session.Character; - var probe = _unitHelper.GetUnitOrThrow(probeEid); + var probe = _unitHelper.GetUnitOrThrow(probeEid); probe.HasAccess(character).ThrowIfError(); probe.Name = probeName; probe.Save(); diff --git a/src/Perpetuum.RequestHandlers/ReimburseItem.cs b/src/Perpetuum.RequestHandlers/ReimburseItem.cs new file mode 100644 index 000000000..6fa351840 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/ReimburseItem.cs @@ -0,0 +1,68 @@ +using Perpetuum.Accounting; +using Perpetuum.Accounting.Characters; +using Perpetuum.Data; +using Perpetuum.Host.Requests; +using Perpetuum.Items.Templates; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace Perpetuum.RequestHandlers +{ + public class ReimburseItemRequestHandler : IRequestHandler + { + private readonly IRobotTemplateReader _robotTemplateReader; + private readonly IAccountManager _accountManager; + private readonly CreateItemRequestHandler _createItemRequestHandler; + + public ReimburseItemRequestHandler(IRobotTemplateReader robotTemplateReader, IAccountManager accountManager) + { + _robotTemplateReader = robotTemplateReader; + _accountManager = accountManager; + _createItemRequestHandler = new CreateItemRequestHandler(_robotTemplateReader); + } + + public void HandleRequest(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var character = Character.Get(request.Data.GetOrDefault(k.characterID)); + var targetContainer = character.GetPublicContainerWithItems(); + var item = _createItemRequestHandler.CreateItem(request).ThrowIfNull(ErrorCodes.WTFErrorMedicalAttentionSuggested); + item.Owner = character.Eid; + + string type = (item is Robots.Robot) ? "Robot" : "Item"; + + LogReimbursement(request, type, item.ED.Definition, item.Quantity); + + targetContainer.AddItem(item, false); + targetContainer.Save(); + + Message.Builder.FromRequest(request) + .WithData(item.ToDictionary()) + .WrapToResult() + .Send(); + + Transaction.Current.OnCommited(() => character.ReloadContainerOnZoneAsync()); + + scope.Complete(); + } + } + + private void LogReimbursement(IRequest request, string ItemType, int itemid, int qty) + { + + Db.Query().CommandText("INSERT INTO opp_reimburselog (ReimburseTo, ReimburseBy, ReimburseTime, EntityId, ItemType, Qty) VALUES (@ReimburseTo, @ReimburseBy, @ReimburseTime, @EntityId, @ItemType, @Qty)") + .SetParameter("@ReimburseTo", request.Data.GetOrDefault(k.characterID)) + .SetParameter("@ReimburseBy", request.Session.Character.Id) + .SetParameter("@ReimburseTime", DateTime.Now) + .SetParameter("@EntityId", itemid) + .SetParameter("@ItemType", ItemType) + .SetParameter("@Qty", qty) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/RelayClose.cs b/src/Perpetuum.RequestHandlers/RelayClose.cs index 55675dcdb..22e0a2b2e 100644 --- a/src/Perpetuum.RequestHandlers/RelayClose.cs +++ b/src/Perpetuum.RequestHandlers/RelayClose.cs @@ -14,7 +14,9 @@ public RelayClose(IRelayStateService relayStateService) public void HandleRequest(IRequest request) { + _relayStateService.ConfigOnlyAllowAdmins(true); _relayStateService.State = RelayState.OpenForAdminsOnly; + Message.Builder.FromRequest(request).WithOk().Send(); } } diff --git a/src/Perpetuum.RequestHandlers/RelayOpen.cs b/src/Perpetuum.RequestHandlers/RelayOpen.cs index 460e2af1d..3179dae94 100644 --- a/src/Perpetuum.RequestHandlers/RelayOpen.cs +++ b/src/Perpetuum.RequestHandlers/RelayOpen.cs @@ -14,7 +14,9 @@ public RelayOpen(IRelayStateService relayStateService) public void HandleRequest(IRequest request) { + _relayStateService.ConfigOnlyAllowAdmins(false); _relayStateService.State = RelayState.OpenForPublic; + Message.Builder.FromRequest(request).WithOk().Send(); } } diff --git a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs index 33a517df0..c28f4862d 100644 --- a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs @@ -27,9 +27,17 @@ public void HandleRequest(IRequest request) var account = LoadAccount(request); if (account == null) throw new PerpetuumException(ErrorCodes.NoSuchUser); - + + if (_relayStateService.State.Equals(RelayState.OpenForAdminsOnly) && !account.AccessLevel.IsAdminOrGm()) + { + throw new PerpetuumException(ErrorCodes.RelayIsClosedForPublic); + } + // ignored in standalone //account.EmailConfirmed.ThrowIfFalse(ErrorCodes.EmailNotConfirmed); + request.Session.SteamBuild = request.Data.GetOrDefault(k.steambuildid); + request.Session.ClientVersion = request.Data.GetOrDefault(k.clientver); + var isLoggedIn = account.IsLoggedIn; if (isLoggedIn) @@ -59,7 +67,8 @@ public void HandleRequest(IRequest request) } var hwHash = request.Data.GetOrDefault(k.hash); - _loginQueueService.EnqueueAccount(request.Session, account.Id, hwHash); + var language = request.Data.GetOrDefault(k.language, 0); + _loginQueueService.EnqueueAccount(request.Session, account.Id, hwHash, language); } protected abstract Account LoadAccount(IRequest request); diff --git a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs index 2d5f52893..b677520ad 100644 --- a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs +++ b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs @@ -1,5 +1,3 @@ -using System.Linq; -using System.Transactions; using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers.Transaction; using Perpetuum.Data; @@ -10,6 +8,8 @@ using Perpetuum.Robots; using Perpetuum.Services.ExtensionService; using Perpetuum.Services.Sparks.Teleports; +using System.Linq; +using System.Transactions; namespace Perpetuum.RequestHandlers.Sparks { @@ -18,7 +18,7 @@ public class SparkTeleportUse : IRequestHandler private readonly IExtensionReader _extensionReader; private readonly SparkTeleportHelper _sparkTeleportHelper; - public SparkTeleportUse(IExtensionReader extensionReader,SparkTeleportHelper sparkTeleportHelper) + public SparkTeleportUse(IExtensionReader extensionReader, SparkTeleportHelper sparkTeleportHelper) { _extensionReader = extensionReader; _sparkTeleportHelper = sparkTeleportHelper; @@ -32,19 +32,19 @@ public void HandleRequest(IRequest request) var character = request.Session.Character; character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); - character.CheckNextAvailableUndockTimeAndThrowIfFailed(); - CheckExtensionLevelAndThrowIfFailed(character); + //character.CheckNextAvailableUndockTimeAndThrowIfFailed(); + //CheckExtensionLevelAndThrowIfFailed(character); - var sparkTeleport = _sparkTeleportHelper.Get(id); + var sparkTeleport = _sparkTeleportHelper.GetCommon(id); var currentDockingBase = character.GetCurrentDockingBase(); - character.SubtractFromWallet(TransactionType.SparkTeleportUse, SparkTeleport.SPARK_TELEPORT_USE_FEE); + //character.SubtractFromWallet(TransactionType.SparkTeleportUse, SparkTeleport.SPARK_TELEPORT_USE_FEE); - if ( sparkTeleport.DockingBase == currentDockingBase) + if (sparkTeleport.DockingBase == currentDockingBase) throw new PerpetuumException(ErrorCodes.YouAreHereAlready); sparkTeleport.DockingBase.IsDockingAllowed(character).ThrowIfError(); - sparkTeleport.DockingBase.DockIn(character,Player.NormalUndockDelay); + sparkTeleport.DockingBase.DockIn(character, Player.NormalUndockDelay); var robot = sparkTeleport.DockingBase.GetPublicContainerWithItems(character) .GetFullTree() @@ -61,11 +61,11 @@ public void HandleRequest(IRequest request) .WithData(sparkTeleport.ToDictionary()) .Send(); }); - + scope.Complete(); } } - + private void CheckExtensionLevelAndThrowIfFailed(Character character) { var liveSparkTeleports = _sparkTeleportHelper.GetAllSparkTeleports(character); diff --git a/src/Perpetuum.RequestHandlers/Standings/SetStanding.cs b/src/Perpetuum.RequestHandlers/Standings/SetStanding.cs index b0818fc25..46c49d37b 100644 --- a/src/Perpetuum.RequestHandlers/Standings/SetStanding.cs +++ b/src/Perpetuum.RequestHandlers/Standings/SetStanding.cs @@ -115,8 +115,6 @@ public void HandleRequest(IRequest request) .ToCorporation(targetCorporation, roleMask) .Send(); } - - return; } Message.Builder.FromRequest(request) diff --git a/src/Perpetuum.RequestHandlers/TeleportGetChannelList.cs b/src/Perpetuum.RequestHandlers/TeleportGetChannelList.cs index ed924bfcb..804741cf4 100644 --- a/src/Perpetuum.RequestHandlers/TeleportGetChannelList.cs +++ b/src/Perpetuum.RequestHandlers/TeleportGetChannelList.cs @@ -1,4 +1,5 @@ using Perpetuum.Host.Requests; +using Perpetuum.Items; using Perpetuum.Zones; using Perpetuum.Zones.Teleporting; @@ -19,6 +20,19 @@ public void HandleRequest(IZoneRequest request) var teleport = _zoneManager.GetUnit(teleportEid); if (teleport == null) throw new PerpetuumException(ErrorCodes.TeleportNotFound); + + //Check if mobile -- Throw if character is not owner, or in owner's gang + if (teleport is MobileTeleport mobile) + { + var character = request.Session.Character; + var ownerCharacter = mobile.GetOwnerAsCharacter(); + if (character != ownerCharacter) + { + var playerGang = character.GetGang(); + playerGang.ThrowIfNull(ErrorCodes.CharacterNotInGang); + playerGang.IsMember(ownerCharacter).ThrowIfFalse(ErrorCodes.CharacterNotInTheOwnerGang); + } + } var result = teleport.ToDictionary(); Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); diff --git a/src/Perpetuum.RequestHandlers/TransportAssignments/TransportAssignmentTake.cs b/src/Perpetuum.RequestHandlers/TransportAssignments/TransportAssignmentTake.cs index 9b698ce61..e1f7874f4 100644 --- a/src/Perpetuum.RequestHandlers/TransportAssignments/TransportAssignmentTake.cs +++ b/src/Perpetuum.RequestHandlers/TransportAssignments/TransportAssignmentTake.cs @@ -33,13 +33,13 @@ private static void TakeTransportAssignment(int id, Character character) var transportAssignmentInfo = TransportAssignment.Get(id); transportAssignmentInfo.ownercharacter.ThrowIfEqual(character, ErrorCodes.WTFErrorMedicalAttentionSuggested); - transportAssignmentInfo.taken.ThrowIfTrue(ErrorCodes.TransportAssignmentIsTaken); - transportAssignmentInfo.volunteercharacter.ThrowIfNotEqual(null, ErrorCodes.TransportAssignmentIsTaken); - - transportAssignmentInfo.TakeCollateral(character); + transportAssignmentInfo.taken.ThrowIfTrue(ErrorCodes.TransportAssignmentIsTaken); + transportAssignmentInfo.expiry.ThrowIfLess(DateTime.Now, ErrorCodes.TransportAssignmentExpired); transportAssignmentInfo.volunteercharacter = character; + transportAssignmentInfo.volunteercharacter.ThrowIfNotEqual(character, ErrorCodes.TransportAssignmentIsTaken); transportAssignmentInfo.taken = true; + transportAssignmentInfo.TakeCollateral(character); transportAssignmentInfo.started = DateTime.Now; transportAssignmentInfo.UpdateToDb(); diff --git a/src/Perpetuum.RequestHandlers/UpdateNews.cs b/src/Perpetuum.RequestHandlers/UpdateNews.cs new file mode 100644 index 000000000..6f06b9e04 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/UpdateNews.cs @@ -0,0 +1,39 @@ +using Perpetuum.Data; +using Perpetuum.Host.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.RequestHandlers +{ + public class UpdateNews : IRequestHandler + { + public void HandleRequest(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var title = request.Data.GetOrDefault(k.title); + var body = request.Data.GetOrDefault(k.body); + var type = request.Data.GetOrDefault(k.type); + var language = request.Data.GetOrDefault(k.language); + var time = request.Data.GetOrDefault(k.time); + var idx = request.Data.GetOrDefault(k.ID); + + Db.Query().CommandText("UPDATE news SET [title]=@title, [body]=@body, [type]=@type, [language]=@language, [ntime]=@ntime WHERE [idx]=@idx") + .SetParameter("@title", title) + .SetParameter("@body", body) + .SetParameter("@type", type) + .SetParameter("@language", language) + .SetParameter("@ntime", time) + .SetParameter("@idx", idx) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); + + Message.Builder.FromRequest(request).WithOk().Send(); + + scope.Complete(); + } + } + } +} diff --git a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index f6799a35a..9637c4512 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -1,5 +1,9 @@ +using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using System.Transactions; +using Perpetuum.Accounting; using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers.Transaction; using Perpetuum.Containers; @@ -7,19 +11,66 @@ using Perpetuum.EntityFramework; using Perpetuum.Host.Requests; using Perpetuum.Items; +using Perpetuum.Players; +using Perpetuum.RequestHandlers.Extensions; +using Perpetuum.Robots; +using Perpetuum.Services.ExtensionService; +using Perpetuum.Services.Sparks.Teleports; +using Perpetuum.Zones; namespace Perpetuum.RequestHandlers { public class UseLotteryItem : IRequestHandler { - private readonly IEntityServices _entityServices; + private readonly IEntityServices entityServices; + private readonly IAccountManager accountManager; + private readonly IAccountRepository accountRepository; + private readonly SparkTeleportHelper sparkTeleportHelper; - public UseLotteryItem(IEntityServices entityServices) + public UseLotteryItem( + IEntityServices entityServices, + IAccountManager accountManager, + IAccountRepository accountRepository, + SparkTeleportHelper sparkTeleportHelper) { - _entityServices = entityServices; + this.entityServices = entityServices; + this.accountManager = accountManager; + this.accountRepository = accountRepository; + this.sparkTeleportHelper = sparkTeleportHelper; } public void HandleRequest(IRequest request) + { + var itemEid = request.Data.GetOrDefault(k.itemEID); + var item = entityServices.Repository.Load(itemEid); + + if (item is LotteryItem) + { + HandleLottery(request); + } + else if (item is EPBoost) + { + HandleEPBoost(request, itemEid); + } + else if (item is Paint) //TODO this is here until we can build a good category flag.. + { + HandlePaint(request, itemEid); + } + else if (item is CalibrationProgramCapsule) + { + HandleCalibrationTemplateItem(request, itemEid); + } + else if (item is RespecToken) + { + HandleRespecToken(request, itemEid); + } + else if (item is SparkTeleportDevice) + { + HandleSparkTeleportDevice(request, itemEid); + } + } + + private void HandleLottery(IRequest request) { using (var scope = Db.CreateTransaction()) { @@ -31,11 +82,11 @@ public void HandleRequest(IRequest request) var lotteryItem = (LotteryItem)container.GetItemOrThrow(itemEid, true).Unstack(1); var randomEd = lotteryItem.PickRandomItem(); - var randomItem = (Item)_entityServices.Factory.CreateWithRandomEID(randomEd); + var randomItem = (Item)entityServices.Factory.CreateWithRandomEID(randomEd); randomItem.Owner = character.Eid; - container.AddItem(randomItem,true); - _entityServices.Repository.Delete(lotteryItem); + container.AddItem(randomItem, true); + entityServices.Repository.Delete(lotteryItem); container.Save(); LogOpen(character, container, lotteryItem); @@ -51,27 +102,248 @@ public void HandleRequest(IRequest request) Message.Builder.FromRequest(request).WithData(result).Send(); }); - + + scope.Complete(); + } + } + + private void HandleEPBoost(IRequest request, long itemEid) + { + using (var scope = Db.CreateTransaction()) + { + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; + var account = accountManager.Repository + .Get(request.Session.AccountId) + .ThrowIfNull(ErrorCodes.AccountNotFound); + + var container = Container.GetWithItems(containerEid, character); + + var containerItem = (EPBoost)container.GetItemOrThrow(itemEid, true).Unstack(1); + + containerItem.Activate(accountManager, account); + + entityServices.Repository.Delete(containerItem); + container.Save(); + LogActivation(character, container, containerItem); + + Transaction.Current.OnCommited(() => + { + //Send custom message back in "Redeemables" dialog + var boostDict = containerItem.ToDictionary(); + boostDict[k.quantity] = -1; //Indicate the consumption of item from stack + var result = new Dictionary + { + { k.container, container.ToDictionary() }, + { k.item, boostDict } + }; + Message.Builder.FromRequest(request).WithData(result).Send(); + }); + + scope.Complete(); + } + } + + private void HandleCalibrationTemplateItem(IRequest request, long itemEid) + { + using (var scope = Db.CreateTransaction()) + { + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; + character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); + + var container = Container.GetWithItems(containerEid, character); + container.ThrowIfNotType(ErrorCodes.ContainerHasToBeOnADockingBase); //Error for unpacking elsewhere + + var ctCapsule = (CalibrationProgramCapsule)container.GetItemOrThrow(itemEid, true).Unstack(1); + var ctDef = ctCapsule.Activate(); + + var ctItem = (Item)entityServices.Factory.CreateWithRandomEID(ctDef); + ctItem.Owner = character.Eid; + + container.AddItem(ctItem, false); // CTs dont stack + entityServices.Repository.Delete(ctCapsule); + container.Save(); + + Transaction.Current.OnCommited(() => + { + var result = new Dictionary + { + { k.container, container.ToDictionary() }, + { k.item, ctItem.ToDictionary() } + }; + Message.Builder.FromRequest(request).WithData(result).Send(); + }); + scope.Complete(); } } private static void LogOpen(Character character, Container container, LotteryItem lotteryItem) { - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.LotteryOpen) - .SetCharacter(character) - .SetContainer(container) - .SetItem(lotteryItem); + var b = TransactionLogEvent.Builder() + .SetTransactionType(TransactionType.LotteryOpen) + .SetCharacter(character) + .SetContainer(container) + .SetItem(lotteryItem); + + character.LogTransaction(b); + } + + private static void LogActivation(Character character, Container container, Item item) + { + var b = TransactionLogEvent.Builder() + .SetTransactionType(TransactionType.ItemRedeem) + .SetCharacter(character) + .SetContainer(container) + .SetItem(item); + character.LogTransaction(b); } private static void LogRandomItemCreated(Character character, Container container, Item randomItem) { - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.LotteryRandomItemCreated) - .SetCharacter(character) - .SetContainer(container) - .SetItem(randomItem); + var b = TransactionLogEvent.Builder() + .SetTransactionType(TransactionType.LotteryRandomItemCreated) + .SetCharacter(character) + .SetContainer(container) + .SetItem(randomItem); + character.LogTransaction(b); } + + private void HandlePaint(IRequest request, long paintEid) + { + using (var scope = Db.CreateTransaction()) + { + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; + character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); + + var container = Container.GetWithItems(containerEid, character); + container.ThrowIfNotType(ErrorCodes.RobotMustBeSelected); //TODO better error to indicate item not being activated in robot cargo + + var paintItem = (Paint)container.GetItemOrThrow(paintEid, true).Unstack(1); + paintItem.Activate(container as RobotInventory, character); + entityServices.Repository.Delete(paintItem); + container.Save(); + + Transaction.Current.OnCommited(() => + { + //Send custom message back in "Redeemables" dialog + var paintDict = paintItem.ToDictionary(); + paintDict[k.quantity] = -1; //Indicate the consumption of item from stack + var result = new Dictionary + { + { k.container, container.ToDictionary() }, + { k.item, paintDict} + }; + Message.Builder.FromRequest(request).WithData(result).Send(); + }); + + scope.Complete(); + } + } + + private void HandleRespecToken(IRequest request, long itemEid) + { + using (var scope = Db.CreateTransaction()) + { + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; + var account = accountManager.Repository + .Get(request.Session.AccountId) + .ThrowIfNull(ErrorCodes.AccountNotFound); + + (character.IsDocked).ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); + + var container = Container.GetWithItems(containerEid, character); + var containerItem = (RespecToken)container + .GetItemOrThrow(itemEid, true) + .Unstack(1); + + containerItem.Activate(accountManager, account, character); + + entityServices.Repository.Delete(containerItem); + container.Save(); + LogActivation(character, container, containerItem); + + Transaction.Current.OnCommited(() => + { + accountRepository.Update(account); + }); + + scope.Complete(); + } + + var relogMessage = new Dictionary + { + { k.message, "You will be automatically relogged in 5 seconds" }, + { k.translate, "relog_in_5_seconds" }, + }; + + Message.Builder + .SetCommand(Commands.ServerMessage) + .WithData(relogMessage) + .ToCharacter(request.Session.Character) + .Send(); + + var delay = TimeSpan.FromSeconds(5); + + Task.Delay(delay).ContinueWith(t => + { + using (var scope = Db.CreateTransaction()) + { + var session = request.Session; + session.DeselectCharacter(); + scope.Complete(); + } + }); + } + + private void HandleSparkTeleportDevice(IRequest request, long itemEid) + { + var baseId = 0; + using (var scope = Db.CreateTransaction()) + { + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; + var account = accountManager.Repository + .Get(request.Session.AccountId) + .ThrowIfNull(ErrorCodes.AccountNotFound); + + (character.IsDocked).ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); + + var container = Container.GetWithItems(containerEid, character); + var containerItem = (SparkTeleportDevice)container + .GetItemOrThrow(itemEid, true) + .Unstack(1); + + baseId = containerItem.BaseId; + + entityServices.Repository.Delete(containerItem); + container.Save(); + LogActivation(character, container, containerItem); + + Transaction.Current.OnCommited(() => + { + accountRepository.Update(account); + }); + + scope.Complete(); + } + + var sparkTeleportData = new Dictionary + { + { k.ID, baseId }, + }; + + var sparkTeleportRequest = new Request(); + sparkTeleportRequest.Command = Commands.SparkTeleportUse; + sparkTeleportRequest.Session = request.Session; + sparkTeleportRequest.Data = sparkTeleportData; + + request.Session.HandleLocalRequest(sparkTeleportRequest); + } } } \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs index db1769ed8..d0eaefcb3 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs @@ -1,74 +1,30 @@ -using System.Collections.Generic; -using System.Transactions; -using Perpetuum.Data; -using Perpetuum.EntityFramework; +using Perpetuum.Containers; using Perpetuum.Host.Requests; using Perpetuum.Items; using Perpetuum.Modules; +using Perpetuum.Players; using Perpetuum.Robots; -using Perpetuum.Zones; namespace Perpetuum.RequestHandlers.Zone.Containers { - public class EquipModule : ZoneContainerRequestHandler + public class EquipModule : ZoneChangeModule { - private readonly IEntityRepository _entityRepository; - - public EquipModule(IEntityRepository entityRepository) - { - _entityRepository = entityRepository; - } - - public override void HandleRequest(IZoneRequest request) + public override void DoChange(IZoneRequest request, Player player, Container container) { - using (var scope = Db.CreateTransaction()) - { - var character = request.Session.Character; - var player = request.Zone.GetPlayerOrThrow(character); - - CheckPvpState(player).ThrowIfError(); - CheckCombatState(player).ThrowIfError(); - CheckActiveModules(player).ThrowIfError(); - - var containerEid = request.Data.GetOrDefault(k.containerEID); - var container = request.Zone.FindContainerOrThrow(player, containerEid); - - CheckContainerType(container).ThrowIfError(); - CheckFieldTerminalRange(player, container).ThrowIfError(); - - player.EnlistTransaction(); - container.EnlistTransaction(); - - Transaction.Current.OnCompleted(completed => - { - player.Initialize(character); - player.SendRefreshUnitPacket(); - - var result = new Dictionary - { - {k.robot, player.ToDictionary()}, - {k.container, container.ToDictionary()} - }; - Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); - }); - - var componentType = request.Data.GetOrDefault(k.robotComponent).ToEnum(); - var component = player.GetRobotComponentOrThrow(componentType); - var slot = request.Data.GetOrDefault(k.slot); - component.MakeSlotFree(slot, container); + var componentType = request.Data.GetOrDefault(k.robotComponent).ToEnum(); + var component = player.GetRobotComponentOrThrow(componentType); + var slot = request.Data.GetOrDefault(k.slot); + component.GetModule(slot).ThrowIfNotNull(ErrorCodes.UsedSlot); //OPP: explicitly prohibit this to make this simpler + // component.MakeSlotFree(slot, container); // Big nope - var moduleEid = request.Data.GetOrDefault(k.moduleEID); - var module = (Module)container.GetItemOrThrow(moduleEid).Unstack(1); - component.EquipModuleOrThrow(module, slot); + var moduleEid = request.Data.GetOrDefault(k.moduleEID); + var module = (Module)container.GetItemOrThrow(moduleEid).Unstack(1); + module.CheckEnablerExtensionsAndThrowIfFailed(player.Character); - player.Initialize(character); - player.CheckEnergySystemAndThrowIfFailed(); + //Perform pre-fit check for fitting legality + player.CheckEnergySystemAndThrowIfFailed(module); - player.Save(); - container.Save(); - - scope.Complete(); - } + component.EquipModuleOrThrow(module, slot); } } } \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs index e867cc7bf..74a5c23d2 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs @@ -1,61 +1,18 @@ -using System.Collections.Generic; -using System.Transactions; -using Perpetuum.Data; +using Perpetuum.Containers; using Perpetuum.Host.Requests; -using Perpetuum.Items; -using Perpetuum.Zones; +using Perpetuum.Players; namespace Perpetuum.RequestHandlers.Zone.Containers { - public class RemoveModule : ZoneContainerRequestHandler + public class RemoveModule : ZoneChangeModule { - public override void HandleRequest(IZoneRequest request) + public override void DoChange(IZoneRequest request, Player player, Container container) { - using (var scope = Db.CreateTransaction()) - { - var character = request.Session.Character; - var player = request.Zone.GetPlayerOrThrow(character); - - CheckPvpState(player).ThrowIfError(); - CheckCombatState(player).ThrowIfError(); - CheckActiveModules(player).ThrowIfError(); - - var containerEid = request.Data.GetOrDefault(k.containerEID); - var container = request.Zone.FindContainerOrThrow(player, containerEid); - - CheckContainerType(container).ThrowIfError(); - CheckFieldTerminalRange(player, container).ThrowIfError(); - - player.EnlistTransaction(); - container.EnlistTransaction(); - - Transaction.Current.OnCompleted(completed => - { - player.Initialize(character); - player.SendRefreshUnitPacket(); - - var result = new Dictionary - { - {k.robot, player.ToDictionary()}, - {k.container, container.ToDictionary()} - }; - - Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); - }); - - var moduleEid = request.Data.GetOrDefault(k.moduleEID); - var module = player.GetModule(moduleEid).ThrowIfNull(ErrorCodes.ModuleNotFound); - module.Owner = character.Eid; - module.Unequip(container); - - player.Initialize(character); - player.CheckEnergySystemAndThrowIfFailed(); - - player.Save(); - container.Save(); - - scope.Complete(); - } + var moduleEid = request.Data.GetOrDefault(k.moduleEID); + var module = player.GetModule(moduleEid).ThrowIfNull(ErrorCodes.ModuleNotFound); + player.CheckEnergySystemAndThrowIfFailed(module, true); + module.Owner = player.Character.Eid; + module.Unequip(container); } } } \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index e43d7f7b3..8e7757332 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -1,50 +1,119 @@ +using System; using System.Collections.Generic; using System.Transactions; +using Perpetuum.Accounting.Characters; +using Perpetuum.Containers; using Perpetuum.Data; using Perpetuum.Host.Requests; using Perpetuum.Items; -using Perpetuum.Robots; +using Perpetuum.Players; using Perpetuum.Zones; namespace Perpetuum.RequestHandlers.Zone.Containers { - public class ZoneChangeModule : ZoneContainerRequestHandler + public abstract class ZoneChangeModule : ZoneContainerRequestHandler { + /// + /// Equip or Remove module based on implementing class + /// Must be called inside of transaction in HandleRequest + /// + public abstract void DoChange(IZoneRequest request, Player player, Container container); + public override void HandleRequest(IZoneRequest request) { using (var scope = Db.CreateTransaction()) { var character = request.Session.Character; - var player = request.Zone.GetPlayerOrThrow(character); + var player = GetPlayer(request, character); - CheckPvpState(player).ThrowIfError(); - CheckCombatState(player).ThrowIfError(); - CheckActiveModules(player).ThrowIfError(); + CheckPlayerState(player); - player.EnlistTransaction(); + var container = GetContainer(request, player); - var componentType = request.Data.GetOrDefault(k.sourceComponent).ToEnum(); - var component = player.GetRobotComponentOrThrow(componentType); - var sourceSlot = request.Data.GetOrDefault(k.source); - var targetSlot = request.Data.GetOrDefault(k.target); - component.ChangeModuleOrThrow(sourceSlot, targetSlot); + CheckContainer(player, container); - player.Initialize(character); - player.Save(); + EquipChange(request, player, container, character); + + SendClientMessageOnComplete(request, player, container); - Transaction.Current.OnCommited(() => - { - player.SendRefreshUnitPacket(); - - var result = new Dictionary - { - { k.robot, player.ToDictionary() } - }; - Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); - }); - scope.Complete(); } } + + private Player GetPlayer(IZoneRequest request, Character character) + { + return request.Zone.GetPlayerOrThrow(character); + } + + private Container GetContainer(IZoneRequest request, Player player) + { + var containerEid = request.Data.GetOrDefault(k.containerEID); + return request.Zone.FindContainerOrThrow(player, containerEid); + } + + private void CheckPlayerState(Player player) + { + CheckPvpState(player).ThrowIfError(); + CheckCombatState(player).ThrowIfError(); + CheckActiveModules(player).ThrowIfError(); + } + + private void CheckContainer(Player player, Container container) + { + CheckContainerType(container).ThrowIfError(); + CheckFieldTerminalRange(player, container).ThrowIfError(); + } + + private void EquipChange(IZoneRequest request, Player player, Container container, Character character) + { + // Enlist transactions on Entities + player.EnlistTransaction(); + container.EnlistTransaction(); + + // Store ratios before transaction + var hpRatio = player.ArmorPercentage; + var apRatio = player.CorePercentage; + + // Call equip/remove + DoChange(request, player, container); + player.OnEquipChange(); + player.Initialize(character); + + player.CheckEnergySystemAndThrowIfFailed(); + + // Apply ratios to prevent resetting values + player.Armor = player.ArmorMax * hpRatio; + player.Core = player.CoreMax * apRatio; + + player.DynamicProperties.Update(k.armor, hpRatio); + player.DynamicProperties.Update(k.currentCore, player.Core); + + // Save changes to entities + player.Save(); + container.Save(); + } + + private void SendClientMessageOnComplete(IZoneRequest request, Player player, Container container) + { + var message = BuildMessage(request, player, container); + Transaction.Current.OnCompleted(completed => OnCompleted(player, message)); + } + + private MessageBuilder BuildMessage(IZoneRequest request, Player player, Container container) + { + var result = new Dictionary + { + { k.robot, player.ToDictionary() }, + { k.container, container.ToDictionary() } + }; + return Message.Builder.FromRequest(request).WithData(result).WrapToResult(); + } + + private static void OnCompleted(Player player, MessageBuilder message) + { + player.Initialize(player.Character); + player.SendRefreshUnitPacket(); + message.Send(); + } } } \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs index 353442e50..55287740c 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs @@ -1,5 +1,6 @@ using System.Linq; using Perpetuum.Containers; +using Perpetuum.EntityFramework; using Perpetuum.Host.Requests; using Perpetuum.Modules; using Perpetuum.Players; @@ -46,17 +47,42 @@ protected static ErrorCodes CheckCombatState(Player player) protected static ErrorCodes CheckFieldTerminalRange(Player player, Container container) { - var fieldTerminal = container.ParentEntity as FieldTerminal; + var fieldTerminal = GetFieldTerminal(container); if (fieldTerminal == null) return ErrorCodes.NoError; - var inRange = player.IsInRangeOf3D(fieldTerminal, DistanceConstants.FIELD_TERMINAL_USE); - if (!inRange) + if (!InRange(player, fieldTerminal)) return ErrorCodes.ItemOutOfRange; return ErrorCodes.NoError; } + [CanBeNull] + private static FieldTerminal GetFieldTerminal(Entity container) + { + if (container == null) + return null; + + var maxDepth = 9; + var depth = 0; + var fieldTerminal = container.ParentEntity as FieldTerminal; + while (fieldTerminal == null && depth < maxDepth) + { + container = container.ParentEntity; + if(container == null) + break; + + fieldTerminal = container.ParentEntity as FieldTerminal; + depth++; + } + return fieldTerminal; + } + + private static bool InRange(Player player, FieldTerminal fieldTerminal) + { + return player.IsInRangeOf3D(fieldTerminal, DistanceConstants.FIELD_TERMINAL_USE); + } + protected static ErrorCodes CheckActiveModules(Player player) { var hasNotIdleModule = player.ActiveModules.Any(m => m.State.Type != ModuleStateType.Idle); diff --git a/src/Perpetuum.RequestHandlers/Zone/MovePlayer.cs b/src/Perpetuum.RequestHandlers/Zone/MovePlayer.cs new file mode 100644 index 000000000..7ef0cf49b --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/MovePlayer.cs @@ -0,0 +1,62 @@ +using Perpetuum.Data; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Sessions; +using Perpetuum.Zones; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.Teleporting.Strategies; + + +namespace Perpetuum.RequestHandlers.Zone +{ + public class MovePlayer : IRequestHandler + { + private readonly IZoneManager _zoneManager; + private readonly ITeleportStrategyFactories _teleportStrategyFactories; + private readonly ISessionManager _sessionManager; + + public MovePlayer(IZoneManager zoneManager, ITeleportStrategyFactories teleportStrategyFactories, ISessionManager sessionManager) + { + _zoneManager = zoneManager; + _teleportStrategyFactories = teleportStrategyFactories; + _sessionManager = sessionManager; + } + + public void HandleRequest(IZoneRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var characterid = request.Data.GetOrDefault(k.characterID); + var targetZoneId = request.Data.GetOrDefault(k.zoneID); + var x = request.Data.GetOrDefault(k.x); + var y = request.Data.GetOrDefault(k.y); + + var TargetPosition = new Position(x, y); + var charactersession = _sessionManager.GetByCharacter(characterid); + var targetZone = _zoneManager.GetZone(targetZoneId); + var player = request.Session.ZoneMgr.GetZone((int)charactersession.Character.ZoneId).GetPlayer(charactersession.Character.ActiveRobotEid); + var s = _teleportStrategyFactories.TeleportToAnotherZoneFactory(targetZone); + + // cannot teleport players in training out of a training zone. + // this allows GMs or devs to teleport themselves in and out of training zones. + // player can still be moved around in the training zone. + if (charactersession.Character.IsInTraining() && targetZone.GetType() != typeof(TrainingZone)) + { + Message.Builder.FromRequest(request).WithError(ErrorCodes.AccessDenied).Send(); + return; + } + + var position = new ClosestWalkablePositionFinder(targetZone, TargetPosition); + if (!position.Find(out Position validPosition)) + { + return; + } + s.TargetPosition = validPosition; + s.DoTeleport(player); + + Message.Builder.FromRequest(request).WithOk().Send(); + + scope.Complete(); + } + } + } +} diff --git a/src/Perpetuum.RequestHandlers/Zone/NPCCheckCondition.cs b/src/Perpetuum.RequestHandlers/Zone/NPCCheckCondition.cs index 8ee4b1cd7..0aff050d5 100644 --- a/src/Perpetuum.RequestHandlers/Zone/NPCCheckCondition.cs +++ b/src/Perpetuum.RequestHandlers/Zone/NPCCheckCondition.cs @@ -22,7 +22,7 @@ public void HandleRequest(IZoneRequest request) foreach (var presence in request.Zone.PresenceManager.GetPresences()) { - var presenceType = presence.Configuration.presenceType; + var presenceType = presence.Configuration.PresenceType; if (presenceType != PresenceType.Normal && presenceType != PresenceType.Random) continue; @@ -31,7 +31,7 @@ public void HandleRequest(IZoneRequest request) var checkConditionsInRange = request.Zone.IsTerrainConditionsMatchInRange(npcFlock.SpawnOrigin, npcFlock.Configuration.SpawnRange.Max, 4.0); if (!checkConditionsInRange) { - Logger.Error("consistency error! invalid roaming position: " + npcFlock.Configuration.SpawnOrigin.ToPoint() + " zoneid: " + request.Zone.Id + " presence:" + presence.Configuration.name + " pres ID:" + presence.Configuration.ID + " " + npcFlock.Configuration.Name + " npcflockID:" + npcFlock.Configuration.ID); + Logger.Error("consistency error! invalid roaming position: " + npcFlock.Configuration.SpawnOrigin.ToPoint() + " zoneid: " + request.Zone.Id + " presence:" + presence.Configuration.Name + " pres ID:" + presence.Configuration.ID + " " + npcFlock.Configuration.Name + " npcflockID:" + npcFlock.Configuration.ID); } } } diff --git a/src/Perpetuum.RequestHandlers/Zone/ProximityProbeRemove.cs b/src/Perpetuum.RequestHandlers/Zone/ProximityProbeRemove.cs index 0e8cac639..0825e8e24 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ProximityProbeRemove.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ProximityProbeRemove.cs @@ -12,7 +12,7 @@ public void HandleRequest(IZoneRequest request) { var eid = request.Data.GetOrDefault(k.eid); - var probeBase = request.Zone.GetUnit(eid) as ProximityProbeBase; + var probeBase = request.Zone.GetUnit(eid) as ProximityDeviceBase; if (probeBase == null) return; diff --git a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 6f8ffe41d..0247d3744 100644 --- a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs +++ b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Linq; @@ -25,16 +26,17 @@ namespace Perpetuum.RequestHandlers.Zone.StatsMapDrawing { public partial class ZoneDrawStatMap : IRequestHandler { - private readonly IZone _zone; + private IZone _zone; private readonly IFileSystem _fileSystem; + private IRequest _request; private readonly SaveBitmapHelper _saveBitmapHelper; private readonly MissionDataCache _missionDataCache; private readonly Dictionary> _actions = new Dictionary>(); private string _typeString; + private bool _sendtoclient; - public ZoneDrawStatMap(IZone zone,IFileSystem fileSystem,SaveBitmapHelper saveBitmapHelper,MissionDataCache missionDataCache) + public ZoneDrawStatMap(IFileSystem fileSystem,SaveBitmapHelper saveBitmapHelper,MissionDataCache missionDataCache) { - _zone = zone; _fileSystem = fileSystem; _saveBitmapHelper = saveBitmapHelper; _missionDataCache = missionDataCache; @@ -47,12 +49,25 @@ public ZoneDrawStatMap(IZone zone,IFileSystem fileSystem,SaveBitmapHelper saveBi RegisterCreator("decorblock", CreateDecorBlockingMap); RegisterCreator("electroplant", CreateElectroPlantMap); RegisterCreator("structures", CreateStructuresMap); + RegisterCreator("players", CreatePlayersMap); RegisterCreator("wall", CreateWallMap); RegisterCreator("wallpossible", CreateWallPossibleMap); RegisterCreator("wallplaces", CreateWallPlaces); RegisterCreator("passable", () => _zone.CreatePassableBitmap(Color.White)); RegisterCreator("islandmask", CreateIslandMaskMap); RegisterCreator("controlmap", CreateControlMap); + RegisterCreator("TerraformProtected", CreateControlFlagMap(TerrainControlFlags.TerraformProtected)); + RegisterCreator("PBSTerraformProtected", CreateControlFlagMap(TerrainControlFlags.PBSTerraformProtected)); + RegisterCreator("Roaming", CreateControlFlagMap(TerrainControlFlags.Roaming)); + RegisterCreator("AntiPlant", CreateControlFlagMap(TerrainControlFlags.AntiPlant)); + RegisterCreator("Highway", CreateControlFlagMap(TerrainControlFlags.Highway)); + RegisterCreator("ConcreteA", CreateControlFlagMap(TerrainControlFlags.ConcreteA)); + RegisterCreator("ConcreteB", CreateControlFlagMap(TerrainControlFlags.ConcreteB)); + RegisterCreator("NpcRestricted", CreateControlFlagMap(TerrainControlFlags.NpcRestricted)); + RegisterCreator("PBSHighway", CreateControlFlagMap(TerrainControlFlags.PBSHighway)); + RegisterCreator("SyndicateArea", CreateControlFlagMap(TerrainControlFlags.SyndicateArea)); + RegisterCreator("HighWayCombo", CreateControlFlagMap(TerrainControlFlags.HighWayCombo)); + RegisterCreator("TerraformProtectedCombo", CreateControlFlagMap(TerrainControlFlags.TerraformProtectedCombo)); RegisterCreator("block",CreateBlockingMap); RegisterCreator("plants",CreatePlantsMap); RegisterCreator("placemo",GenerateMissionSpots ); @@ -61,6 +76,7 @@ public ZoneDrawStatMap(IZone zone,IFileSystem fileSystem,SaveBitmapHelper saveBi RegisterCreator("displayspots", DisplaySpots); RegisterCreator("worstspots",DrawWorstSpotsMap); RegisterCreator("alltargets", DrawAllTargetsOnZone); + RegisterCreator(k.groundType, CreateGroundTypeMap); } private void RegisterCreator(string type,Func bitmapFactory) @@ -73,7 +89,7 @@ private void RegisterCreator(string type,Func bitmapFactory) _actions[type] = (r) => CreateAndSave(type, bitmapFactory); } - private void CreateAndSave(string postfix,Func bitmapFactory) + private void CreateAndSave(string postfix, Func bitmapFactory) { var bmp = bitmapFactory(); if (bmp == null) @@ -81,13 +97,30 @@ private void CreateAndSave(string postfix,Func bitmapFactory) bmp.WithGraphics(g => g.DrawString(_zone.Configuration.Name, new Font("Tahoma", 20), Brushes.Red, new PointF(10, 10))); var fileName = "stat_" + postfix; - _saveBitmapHelper.SaveBitmap(_zone,bmp,fileName ); + + if (_sendtoclient) // send to client. + { + using (var ms = new MemoryStream()) + { + bmp.Save(ms, ImageFormat.Png); + var Base64 = Convert.ToBase64String(ms.GetBuffer()); + Message.Builder.FromRequest(_request).SetData("name", fileName).SetData("img", Base64).Send(); + } + } + else // save locally. + { + _saveBitmapHelper.SaveBitmap(_zone, bmp, fileName); + } } public void HandleRequest(IZoneRequest request) { + _zone = request.Zone; + _request = request; var type = request.Data.GetOrDefault(k.type); + bool sendtoclient = request.Data.GetOrDefault("sendtoclient").ToBool(); _typeString = type; //save for later use + _sendtoclient = sendtoclient; var action = _actions.GetOrDefault(type); if (action != null) @@ -132,6 +165,86 @@ public void HandleRequest(IZoneRequest request) DrawMissionTargetLog(request); break; } + case "PlantBonsai": + { + CreatePlantMap(PlantType.Bonsai); + break; + } + case "PlantBushA": + { + CreatePlantMap(PlantType.BushA); + break; + } + case "PlantBushB": + { + CreatePlantMap(PlantType.BushB); + break; + } + case "PlantDevrinol": + { + CreatePlantMap(PlantType.Devrinol); + break; + } + case "PlantGrassA": + { + CreatePlantMap(PlantType.GrassA); + break; + } + case "PlantGrassB": + { + CreatePlantMap(PlantType.GrassB); + break; + } + case "PlantNanoWheat": + { + CreatePlantMap(PlantType.NanoWheat); + break; + } + case "PlantPineTree": + { + CreatePlantMap(PlantType.PineTree); + break; + } + case "PlantPoffeteg": + { + CreatePlantMap(PlantType.Poffeteg); + break; + } + case "PlantQuag": + { + CreatePlantMap(PlantType.Quag); + break; + } + case "PlantRango": + { + CreatePlantMap(PlantType.Rango); + break; + } + case "PlantReed": + { + CreatePlantMap(PlantType.Reed); + break; + } + case "PlantRustBush": + { + CreatePlantMap(PlantType.RustBush); + break; + } + case "PlantSlimeRoot": + { + CreatePlantMap(PlantType.SlimeRoot); + break; + } + case "PlantTitanPlant": + { + CreatePlantMap(PlantType.TitanPlant); + break; + } + case "PlantTreeIron": + { + CreatePlantMap(PlantType.TreeIron); + break; + } } } @@ -219,8 +332,25 @@ private Bitmap CreateNewFlagsMap() }); } + private Bitmap CreatePlayersMap() + { + return CreateAltitudeBitmap().WithGraphics(g => + { + foreach (var unit in _zone.GetCharacters()) + { + var size = 12; + var pen = Pens.Red; + var x = unit.GetPlayerRobotFromZone().CurrentPosition.intX - (size / 2); + var y = unit.GetPlayerRobotFromZone().CurrentPosition.intY - (size / 2); + g.DrawEllipse(pen, x, y, size, size); + const int width = 4; + g.DrawEllipse(Pens.BlueViolet, x, y, width, width); + g.DrawString(unit.Nick, new Font("Tahoma", 12), Brushes.Red, x + 10, y + 10); + } + }); + } private Bitmap CreateNPCMap() { @@ -236,7 +366,7 @@ private void DrawNpcPresencesOnGraphic(Graphics graphics) foreach (var presence in _zone.PresenceManager.GetPresences().OfType()) { graphics.DrawRectangle(Pens.Blue, presence.Area.X1, presence.Area.Y1, presence.Area.Width, presence.Area.Height); - graphics.DrawString(presence.Configuration.name, new Font("Tahoma", 8), Brushes.White, presence.Area.X1, presence.Area.Y1); + graphics.DrawString(presence.Configuration.Name, new Font("Tahoma", 8), Brushes.Red, presence.Area.X1, presence.Area.Y1); } } @@ -254,6 +384,7 @@ private void DrawNpcFlocksOnGraphic(Graphics graphics) graphics.DrawEllipse(Pens.BlueViolet, txSpawnMax, tySpawnMax, widthSpawnMax, widthSpawnMax); graphics.DrawEllipse(Pens.Red, txHomeRange, tyHomeRange, widthHome, widthHome); + graphics.DrawString(flock.Configuration.Name, new Font("Tahoma", 10), Brushes.Red, txSpawnMax, tySpawnMax); } } @@ -323,6 +454,28 @@ private Bitmap CreateElectroPlantMap() } + private Bitmap CreatePlantMap(PlantType plantType) + { + return CreateAltitudeBitmap().ForEach((bmp, x, y) => + { + var plantInfo = _zone.Terrain.Plants.GetValue(x, y); + + if (plantInfo.type != plantType) + return; + + var r = ((255 / 5) * (plantInfo.state)).Clamp(0, 255); + var color = Color.FromArgb(255, r, 128, 0); + + if (plantInfo.state == 0) + { + color = Color.FromArgb(255, 255, 0, 30); + } + + bmp.SetPixel(x, y, color); + }); + + } + private Bitmap CreatePlantsMap() { return _zone.CreateBitmap().ForEach((bmp, x, y) => @@ -491,6 +644,21 @@ private Bitmap CreateIslandMaskMap() }); } + private Func CreateControlFlagMap(TerrainControlFlags flag) + { + return () => + { + return _zone.CreateBitmap().ForEach((bmp, x, y) => + { + var control = _zone.Terrain.Controls.GetValue(x, y); + if (!control.Flags.HasFlag(flag)) + return; + bmp.SetPixel(x, y, Color.White); + }); + }; + } + + private Bitmap CreateControlMap() { return _zone.CreateBitmap().ForEach((bmp, x, y) => @@ -501,6 +669,23 @@ private Bitmap CreateControlMap() }); } + private Bitmap CreateGroundTypeMap() + { + var numGroundTypes = Enum.GetNames(typeof(GroundType)).Length; + var colors = new Color[numGroundTypes]; + var random = new Random(numGroundTypes); + for (var i = 0; i < colors.Length; i++) + { + colors[i] = Color.FromArgb(random.Next(255), random.Next(255), random.Next(255)); + } + return _zone.CreateBitmap().ForEach((bmp, x, y) => + { + var groundType = _zone.Terrain.Plants.GetValue(x, y).groundType; + var c = colors[((int)groundType).Clamp(0, numGroundTypes - 1)]; + bmp.SetPixel(x, y, c); + }); + } + private void CreateMineralBitmaps() { foreach (var mineralLayer in _zone.Terrain.Materials.OfType()) diff --git a/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs b/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs index f225074c7..65fe29740 100644 --- a/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs +++ b/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs @@ -50,6 +50,12 @@ public void HandleRequest(IZoneRequest request) switch (teleport) { + case MobileStrongholdTeleport mobileStrongholdTeleport: + { + mobileStrongholdTeleport.ApplyTeleportCooldownEffect(); + mobileStrongholdTeleport.Activate(player, description); + break; + } case MobileWorldTeleport mobileWorldTeleport: { mobileWorldTeleport.ApplyTeleportCooldownEffect(); @@ -61,6 +67,7 @@ public void HandleRequest(IZoneRequest request) mobileTeleport.ApplyTeleportCooldownEffect(); break; } + } }); @@ -132,6 +139,7 @@ public TeleportPlayerValidator(Player player) public override void VisitTeleport(Teleport teleport) { _player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); + (_player.HasPvpEffect && _player.HasNoTeleportWhilePVP).ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); _player.CurrentPosition.IsInRangeOf3D(teleport.CurrentPosition, Teleport.TeleportRange).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); base.VisitTeleport(teleport); } @@ -158,7 +166,11 @@ public override void VisitMobileTeleport(MobileTeleport teleport) public override void VisitMobileWorldTeleport(MobileWorldTeleport teleport) { - _player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); + VisitMobileTeleport(teleport); + } + + public override void VisitMobileStrongholdTeleport(MobileStrongholdTeleport teleport) + { VisitMobileTeleport(teleport); } } diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneCleanObstacleBlocking.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneCleanObstacleBlocking.cs index eed1f92af..f692cc8b1 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneCleanObstacleBlocking.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneCleanObstacleBlocking.cs @@ -7,6 +7,7 @@ public class ZoneCleanObstacleBlocking : IRequestHandler { public void HandleRequest(IZoneRequest request) { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); request.Zone.Terrain.Blocks.UpdateAll((x, y, bi) => { bi.Obstacle = false; diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs index f31161cb1..f45477f9f 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs @@ -7,14 +7,32 @@ public class ZoneClearLayer : IRequestHandler { public void HandleRequest(IZoneRequest request) { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var layerName = request.Data.GetOrDefault(k.layerName); switch (layerName) { + case k.groundType: + request.Zone.Terrain.Plants.UpdateAll((x, y, pi) => + { + pi.ClearGroundType(); + return pi; + }); + break; case k.plants: request.Zone.Terrain.Plants.UpdateAll((x, y, pi) => { pi.Clear(); + + // Update blocking footprint of plants for this tile + request.Zone.Terrain.Blocks.UpdateValue(x, y, bi => + { + // only reset the height if there is a plant here. + // otherwise we reset blocking heights for decor, etc! + bi.Height = bi.Plant ? 0 : bi.Height; + bi.Plant = false; + return bi; + }); return pi; }); break; diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneCreateIsland.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateIsland.cs index 107404d4f..161977907 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneCreateIsland.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateIsland.cs @@ -8,6 +8,7 @@ public class ZoneCreateIsland : IRequestHandler { public void HandleRequest(IZoneRequest request) { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var low = request.Data.GetOrDefault(k.low); var waterLevel = ZoneConfiguration.WaterLevel; diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs new file mode 100644 index 000000000..861017d48 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs @@ -0,0 +1,81 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Zones; +using System.Threading.Tasks; + +namespace Perpetuum.RequestHandlers.Zone +{ + public class ZoneCreateTerraformLimit : IRequestHandler + { + private void Clear(IZoneRequest request) + { + var zone = request.Zone; + var area = Area.FromRectangle(0, 0, zone.Size.Width, zone.Size.Height); + var workAreas = area.Slice(32); + + Parallel.ForEach(workAreas, (workArea) => + { + var controlArea = zone.Terrain.Controls.GetArea(workArea); + for(int i=0; i< controlArea.Length; i++) + { + controlArea[i].TerraformProtected = false; + controlArea[i].PBSTerraformProtected = false; + } + zone.Terrain.Controls.SetArea(workArea, controlArea); + }); + } + + private void SetRadiusOnTeleports(IZoneRequest request, int radius) + { + var zone = request.Zone; + var maxX = zone.Terrain.Controls.Width-1; + var maxY = zone.Terrain.Controls.Height-1; + var teleports = request.Zone.GetTeleportColumns(); + foreach (var tele in teleports) + { + var center = tele.CurrentPosition.ToPoint(); + var x0 = (center.X - radius).Clamp(0, maxX); + var x1 = (center.X + radius).Clamp(0, maxX); + var y0 = (center.Y - radius).Clamp(0, maxY); + var y1 = (center.Y + radius).Clamp(0, maxY); + + var area = Area.FromRectangle(x0, y0, x1, y1); + var workAreas = area.Slice(32); + Parallel.ForEach(workAreas, (workArea) => + { + foreach (var p in workArea.GetPositions()) + { + if(!p.intX.IsInRange(0, maxY) || !p.intY.IsInRange(0, maxY)) + { + continue; + } + else if (center.Distance(p) < radius) + { + var c = zone.Terrain.Controls[p.intX, p.intY]; + c.TerraformProtected = true; + c.PBSTerraformProtected = true; + zone.Terrain.Controls[p.intX, p.intY] = c; + } + } + }); + } + } + + public void HandleRequest(IZoneRequest request) + { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + var radius = request.Data.GetOrDefault(k.distance); + var mode = request.Data.GetOrDefault(k.mode); + radius = radius.Clamp(0, 500); + + if (mode == "clear") + { + Clear(request); + } + else if (mode == "teleports") + { + SetRadiusOnTeleports(request, radius); + } + Message.Builder.FromRequest(request).WithOk().Send(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawBlockingByDefinition.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawBlockingByDefinition.cs index 7c853f974..ce6ce6a9d 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawBlockingByDefinition.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawBlockingByDefinition.cs @@ -26,7 +26,7 @@ public void HandleRequest(IZoneRequest request) Logger.Error("definition not found:" + definition); continue; } - + Logger.Info($"Blocking all {ed.Name} on zone {request.Zone.Id}"); request.Zone.DrawBlockingByDefinition(ed); } diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs new file mode 100644 index 000000000..a19ff565c --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs @@ -0,0 +1,225 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Players; +using Perpetuum.Zones; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; + +namespace Perpetuum.RequestHandlers.Zone +{ + public class ZoneDrawRamp : IRequestHandler + { + public void HandleRequest(IZoneRequest request) + { + var width = request.Data.GetOrDefault(k.size, 5); + var edge = request.Data.GetOrDefault(k.range, 0.4); + var useMax = request.Data.GetOrDefault(k.max, 1) == 1; + var x = request.Data.GetOrDefault("positionx"); + var y = request.Data.GetOrDefault("positiony"); + var fullBlend = request.Data.GetOrDefault("blend", 1.0); + + width = width.Clamp(0, 40); + + fullBlend = fullBlend.Clamp(); + var zone = request.Zone; + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + + Player player; + if (zone.TryGetPlayer(request.Session.Character, out player)) + { + var targetPosition = new Position(x, y); + var sourcePosition = player.CurrentPosition; + + if (sourcePosition.TotalDistance2D(targetPosition) > 800) + { + throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } + + DrawRamp(zone, sourcePosition, targetPosition, width, edge, fullBlend, useMax); + } + + + } + + static double MixValues(double start, double end, double sampleBetween) + { + var diff = end - start; + var diffAtSample = diff * sampleBetween; + return start + diffAtSample; + } + + private static void DrawRamp(IZone zone, Position sourcePosition, Position targetPosition, int rampWidth, double edge, double fullBlend, bool setIfMax = false) + { + // The Altitude layer is a ushort, but can be read as a decimal. + // The decimal representation can be converted to ushort by dividing by 32 + // I suggest we work with just one unit and do a conversion + var work = new List(); + + sourcePosition = sourcePosition.Center; + targetPosition = targetPosition.Center; + + var terrain = zone.Terrain; + + var subSamples = 5; + + var sourceAltitude = terrain.Altitude.GetAltitudeAsDouble(sourcePosition); + var targetAltitude = terrain.Altitude.GetAltitudeAsDouble(targetPosition); + + var totalDistance = sourcePosition.TotalDistance2D(targetPosition); + + var directionVector = targetPosition - sourcePosition; + var directionUnit = directionVector / directionVector.lengthDouble2D; + var directionMicro = directionUnit / subSamples; + + var directionUnitLeft = directionUnit.RotateAroundOrigo(Math.PI / 2); + var directionUnitRight = directionUnit.RotateAroundOrigo(-1 * Math.PI / 2); + + var leftMicro = directionUnitLeft / subSamples; + var rightMicro = directionUnitRight / subSamples; + + for (var i = 0; i < totalDistance; i++) + { + var centerPosition = (directionUnit * i) + sourcePosition; + + for (var j = 0; j < rampWidth; j++) + { + + for (var k = 0; k < subSamples; k++) + { + + var cMicroPos = directionMicro * k + centerPosition; + + var leftCenter = (directionUnitLeft * j) + cMicroPos; + var rightCenter = (directionUnitRight * j) + cMicroPos; + + var distanceFromStart = cMicroPos.TotalDistance2D(sourcePosition); + var dirBlend = distanceFromStart / totalDistance; + + var dirAltitude = MixValues(sourceAltitude, targetAltitude, dirBlend); + + for (var l = 0; l < subSamples; l++) + { + var cMicroLeft = leftMicro * l + leftCenter; + var cMicroRight = rightMicro * l + rightCenter; + + + var sideDist = cMicroLeft.TotalDistance2D(cMicroPos); + + var blend = sideDist / rampWidth; + + var mix = 0.0; + if (blend > 1 - edge) + { + mix = (blend - (1 - edge)) * 1 / edge; + } + + + var leftRamp = new RampSample + { + altitude = dirAltitude, + mix = mix, + position = cMicroLeft, + }; + + var rightRamp = new RampSample + { + altitude = dirAltitude, + mix = mix, + position = cMicroRight, + }; + + work.Add(rightRamp); + work.Add(leftRamp); + } + } + + + } + + } + + var dict = new Dictionary(); + + Console.WriteLine("drawing " + work.Count + " rampsamples."); + + foreach (var rampSample in work) + { + + var key = rampSample.position.intX + rampSample.position.intY * zone.Size.Width; + + RampSample s; + if (dict.TryGetValue(key, out s)) + { + + s.altitude += rampSample.altitude; + s.mix += rampSample.mix; + s.samples += 1; + + } + else + { + var q = new RampSample + { + altitude = rampSample.altitude, + mix = rampSample.mix, + position = rampSample.position.Center, + samples = 1, + }; + + dict.Add(key, q); + } + } + + using (var terrainUpdateMonitor = new TerrainUpdateMonitor(zone)) + { + int minX = 2048; + int minY = 2048; + int maxX = 0; + int maxY = 0; + foreach (var rampSample in dict.Values) + { + var mix = rampSample.mix / rampSample.samples; + var avgAlt = rampSample.altitude / rampSample.samples; + + var origAlt = zone.Terrain.Altitude.GetAltitudeAsDouble(rampSample.position.intX, rampSample.position.intY); + + var altVal = MixValues(avgAlt, origAlt, mix); + var fullBlended = MixValues(origAlt, altVal, fullBlend); + + var finalAlt = fullBlended; + if (setIfMax) + { + finalAlt = Math.Max(origAlt, fullBlended); + } + if (finalAlt == 0) + { + finalAlt = origAlt; + } + var rx = rampSample.position.intX; + var ry = rampSample.position.intY; + + minX = Math.Min(minX, rx); + maxX = Math.Max(maxX, rx); + minY = Math.Min(minY, ry); + maxY = Math.Max(maxY, ry); + + var shortAlt = System.Convert.ToUInt16(finalAlt * 32); + + zone.Terrain.Altitude.SetValue(rx, ry, shortAlt); + } + zone.Terrain.Slope.UpdateSlopeByArea(new Area(minX, minY, maxX, maxY)); + } + } + + private class RampSample + { + public double altitude; + public double mix; + public Position position; + + public int samples; + + } + + } +} diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneFillGroundTypeRandom.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneFillGroundTypeRandom.cs new file mode 100644 index 000000000..05c193dbe --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneFillGroundTypeRandom.cs @@ -0,0 +1,71 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Log; +using Perpetuum.Zones.Terrains; +using Perpetuum.Zones.Terrains.Materials.Plants; +using System; +using System.Linq; + +namespace Perpetuum.RequestHandlers.Zone +{ + public class ZoneFillGroundTypeRandom : IRequestHandler + { + private bool IsGroundTypeFilled(ILayer plants) + { + return !plants.RawData.Any(p => p.groundType == GroundType.undefined); + } + + private void SetGroundTypeWithCircle(ILayer plants, Position center, int radius, GroundType groundType) + { + var x0 = (center.intX - radius).Clamp(0, plants.Width - 1); + var x1 = (center.intX + radius).Clamp(0, plants.Width); + var y0 = (center.intY - radius).Clamp(0, plants.Width - 1); + var y1 = (center.intY + radius).Clamp(0, plants.Width); + var period = FastRandom.NextInt(3, 32); + var fuzzPercent = FastRandom.NextDouble(0.05, 0.5); + var noiseMagnitude = FastRandom.NextInt(1, (int)(radius * 0.25)); + for (int x = x0; x < x1; x++) + { + for (int y = y0; y < y1; y++) + { + var p = new Position(x, y); + var angle = center.DirectionTo(p) * Math.PI; + var sin = (Math.Sin(angle * period) + 1.0) * 0.5; + var noise = (FastRandom.NextDouble()-0.5)* noiseMagnitude; + var boundary = radius * (1.0 - fuzzPercent) + (radius * sin * fuzzPercent); + if (center.TotalDistance2D(x, y) > (boundary + noise).Clamp(1, radius)) continue; + var info = plants.GetValue(x, y); + info.SetGroundType(groundType); + plants[x, y] = info; + } + } + } + + private GroundType PickRandom() + { + var values = ((GroundType[])Enum.GetValues(typeof(GroundType))).Where(g => g != GroundType.undefined).ToArray(); + return values[FastRandom.NextInt(values.Length - 1)]; + } + + public void HandleRequest(IZoneRequest request) + { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + var minRadius = request.Data.GetOrDefault(k.size).Clamp(2, 200); + var iterations = request.Data.GetOrDefault(k.numberOfRuns).Clamp(1, 1000); + var zoneRect = request.Zone.Size; + var plants = request.Zone.Terrain.Plants; + Logger.Info($"{request.Zone.Id} filling groundtype with {iterations} and brush radius {minRadius}"); + for (var i = 0; i < iterations; i++) + { + if (i % 100 == 0) + { + Logger.Info($"{request.Zone.Id} filling groundtype iteration {i}/{iterations}..."); + } + var pos = zoneRect.GetRandomPosition(1); + var radius = (int)(FastRandom.NextDouble() * (minRadius * 0.5) + minRadius); + SetGroundTypeWithCircle(plants, pos, radius, PickRandom()); + } + Logger.Info($"{request.Zone.Id} groundtype filled? {IsGroundTypeFilled(plants)}"); + Message.Builder.FromRequest(request).WithOk().Send(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneKillNPlants.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneKillNPlants.cs index baebaaf62..eddfb0adc 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneKillNPlants.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneKillNPlants.cs @@ -25,7 +25,9 @@ public void HandleRequest(IZoneRequest request) request.Zone.Terrain.Blocks.UpdateValue(x, y, bi => { - bi.Height = 0; + // only reset the height if there is a plant here. + // otherwise we reset blocking heights for decor, etc! + bi.Height = bi.Plant ? 0 : bi.Height; bi.Plant = false; return bi; }); diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneSetLayerWithBitMap.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneSetLayerWithBitMap.cs new file mode 100644 index 000000000..6e5ddec8d --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneSetLayerWithBitMap.cs @@ -0,0 +1,41 @@ +using System.Drawing; +using Perpetuum.Host.Requests; +using Perpetuum.IO; +using Perpetuum.Zones; +using Perpetuum.Zones.Terrains; + +namespace Perpetuum.RequestHandlers.Zone +{ + public class ZoneSetLayerWithBitMap : IRequestHandler + { + private readonly IFileSystem _fileSystem; + public ZoneSetLayerWithBitMap(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public void HandleRequest(IZoneRequest request) + { + var zone = request.Zone; + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + var fileName = request.Data.GetOrDefault(k.file); + var flagValue = request.Data.GetOrDefault(k.flags); + var controlFlag = EnumHelper.GetEnum(flagValue); + var path = _fileSystem.CreatePath("bitmaps", zone.CreateTerrainDataFilename(fileName, "png")); + var img = Image.FromFile(path); + using (Bitmap bmp = new Bitmap(img)) + { + zone.Terrain.Controls.UpdateAll((x, y, c) => + { + if (bmp.GetPixel(x, y).A == 0) + { + return c; + } + c.SetFlags(controlFlag, true); + return c; + }); + } + Message.Builder.FromRequest(request).WithOk().Send(); + } + } +} diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneSmooth.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneSmooth.cs new file mode 100644 index 000000000..836435469 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneSmooth.cs @@ -0,0 +1,53 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Zones; +using Perpetuum.Zones.Terrains; +using System.Threading.Tasks; + +namespace Perpetuum.RequestHandlers.Zone +{ + public class ZoneSmooth : IRequestHandler + { + private int CalculateBufferOffset(int x, int y, Area area) + { + return (x - area.X1) + (y - area.Y1) * area.Width; + } + public void HandleRequest(IZoneRequest request) + { + var zone = request.Zone; + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + var area = Area.FromRectangle(0, 0, zone.Size.Width, zone.Size.Height); + var altBuffer = new ushort[area.Ground]; + var workAreas = area.Slice(32); + Parallel.ForEach(workAreas, (workArea) => + { + foreach (var p in workArea.GetPositions()) + { + var sum = 0.0; + var count = 0; + foreach (var n in p.EightNeighbours) + { + if (zone.IsValidPosition(n.intX, n.intY)) + { + sum += zone.Terrain.Altitude.GetAltitudeAsDouble(n.intX, n.intY); + count++; + } + } + if (count > 0) + { + var smoothed = sum / count; + var shortAlt = System.Convert.ToUInt16(smoothed * 32); + altBuffer[CalculateBufferOffset(p.intX, p.intY, area)] = shortAlt; + } + } + }); + Parallel.ForEach(workAreas, (workArea) => + { + foreach (var p in workArea.GetPositions()) + { + zone.Terrain.Altitude.SetValue(p.intX, p.intY, altBuffer[CalculateBufferOffset(p.intX, p.intY, area)]); + } + zone.Terrain.Slope.UpdateSlopeByArea(workArea); + }); + } + } +} diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneTerraformTest.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneTerraformTest.cs index cd6bb8d7c..644995d9e 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneTerraformTest.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneTerraformTest.cs @@ -23,6 +23,7 @@ public class ZoneTerraformTest : IRequestHandler public void HandleRequest(IZoneRequest request) { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var character = request.Session.Character; var player = request.Zone.GetPlayer(character); diff --git a/src/Perpetuum.RequestHandlers/ZoneCopyGroundType.cs b/src/Perpetuum.RequestHandlers/ZoneCopyGroundType.cs new file mode 100644 index 000000000..ba7972f2c --- /dev/null +++ b/src/Perpetuum.RequestHandlers/ZoneCopyGroundType.cs @@ -0,0 +1,43 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Zones; +using System.Threading.Tasks; + +namespace Perpetuum.RequestHandlers +{ + public class ZoneCopyGroundType : IRequestHandler + { + private readonly IZoneManager _zoneManager; + + public ZoneCopyGroundType(IZoneManager zoneManager) + { + _zoneManager = zoneManager; + } + + public void HandleRequest(IRequest request) + { + var sourceZoneId = request.Data.GetOrDefault(k.source); + var targetZoneId = request.Data.GetOrDefault(k.target); + var targetZone = _zoneManager.GetZone(targetZoneId); + var sourceZone = _zoneManager.GetZone(sourceZoneId); + targetZone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + targetZoneId.ThrowIfEqual(sourceZoneId, ErrorCodes.WTFErrorMedicalAttentionSuggested); + targetZone.Size.ThrowIfNotEqual(sourceZone.Size, ErrorCodes.WTFErrorMedicalAttentionSuggested); + + var area = Area.FromRectangle(0, 0, sourceZone.Size.Width, sourceZone.Size.Height); + var altBuffer = new ushort[area.Ground]; + var workAreas = area.Slice(32); + Parallel.ForEach(workAreas, (workArea) => + { + var target = targetZone.Terrain.Plants.GetArea(workArea); + var source = sourceZone.Terrain.Plants.GetArea(workArea); + for (int i=0; i< target.Length; i++) + { + target[i].SetGroundType(source[i].groundType); + } + targetZone.Terrain.Plants.SetArea(workArea, target); + }); + + Message.Builder.FromRequest(request).WithOk().Send(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum.RequestHandlers/ZoneSaveLayer.cs b/src/Perpetuum.RequestHandlers/ZoneSaveLayer.cs index b265cc761..5b89cde02 100644 --- a/src/Perpetuum.RequestHandlers/ZoneSaveLayer.cs +++ b/src/Perpetuum.RequestHandlers/ZoneSaveLayer.cs @@ -6,9 +6,9 @@ namespace Perpetuum.RequestHandlers public class ZoneSaveLayer : IRequestHandler { private readonly IZoneManager _zoneManager; - private readonly LayerFileIO _layerFileIO; + private readonly ILayerFileIO _layerFileIO; - public ZoneSaveLayer(IZoneManager zoneManager,LayerFileIO layerFileIO) + public ZoneSaveLayer(IZoneManager zoneManager, ILayerFileIO layerFileIO) { _zoneManager = zoneManager; _layerFileIO = layerFileIO; @@ -16,15 +16,29 @@ public ZoneSaveLayer(IZoneManager zoneManager,LayerFileIO layerFileIO) public void HandleRequest(IRequest request) { - foreach (var zone in _zoneManager.Zones) + if (request.Data.ContainsKey(k.zoneID)) { - _layerFileIO.SaveLayerToDisk(zone,zone.Terrain.Altitude); - _layerFileIO.SaveLayerToDisk(zone,zone.Terrain.Blocks); - _layerFileIO.SaveLayerToDisk(zone,zone.Terrain.Controls); - _layerFileIO.SaveLayerToDisk(zone,zone.Terrain.Plants); + var zoneId = request.Data.GetOrDefault(k.zoneID); + _zoneManager.ContainsZone(zoneId).ThrowIfFalse(ErrorCodes.ZoneNotFound); + var zone = _zoneManager.GetZone(zoneId); + SaveZoneLayers(zone); + } + else + { + foreach (var zone in _zoneManager.Zones) + { + SaveZoneLayers(zone); + } } - Message.Builder.FromRequest(request).WithOk().Send(); } + + private void SaveZoneLayers(IZone zone) + { + _layerFileIO.SaveLayerToDisk(zone, zone.Terrain.Altitude); + _layerFileIO.SaveLayerToDisk(zone, zone.Terrain.Blocks); + _layerFileIO.SaveLayerToDisk(zone, zone.Terrain.Controls); + _layerFileIO.SaveLayerToDisk(zone, zone.Terrain.Plants); + } } } \ No newline at end of file diff --git a/src/Perpetuum.Server/Perpetuum.Server.csproj.user b/src/Perpetuum.Server/Perpetuum.Server.csproj.user index a15ccb472..6f4cef0a6 100644 --- a/src/Perpetuum.Server/Perpetuum.Server.csproj.user +++ b/src/Perpetuum.Server/Perpetuum.Server.csproj.user @@ -1,9 +1,19 @@  - d:\work\server\genxy + C:\PerpetuumServer\data a:\work\server\genxy + + publish\ + + + + + + en-US + false + \ No newline at end of file diff --git a/src/Perpetuum.ServerService/App.config b/src/Perpetuum.ServerService/App.config new file mode 100644 index 000000000..56c89d4da --- /dev/null +++ b/src/Perpetuum.ServerService/App.config @@ -0,0 +1,18 @@ + + + + +
+ + + + + + + + + C:\PerpetuumServer\data\ + + + + \ No newline at end of file diff --git a/src/Perpetuum.ServerService/PerpServer.Designer.cs b/src/Perpetuum.ServerService/PerpServer.Designer.cs new file mode 100644 index 000000000..5f66ce51d --- /dev/null +++ b/src/Perpetuum.ServerService/PerpServer.Designer.cs @@ -0,0 +1,37 @@ +namespace Perpetuum.ServerService +{ + partial class PerpServer + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "PerpServer"; + } + + #endregion + } +} diff --git a/src/Perpetuum.ServerService/PerpServer.cs b/src/Perpetuum.ServerService/PerpServer.cs new file mode 100644 index 000000000..a4e7ca62e --- /dev/null +++ b/src/Perpetuum.ServerService/PerpServer.cs @@ -0,0 +1,96 @@ +using Perpetuum.Bootstrapper; +using System; +using System.ComponentModel; +using System.IO; +using System.ServiceProcess; +using System.Threading.Tasks; +using Autofac; +using Perpetuum.Host; +using System.Threading; +using Perpetuum.Log; + +namespace Perpetuum.ServerService +{ + public partial class PerpServer : ServiceBase + { + + public PerpServer() + { + InitializeComponent(); + Bootstrapper = new PerpetuumBootstrapper(); + + base.CanShutdown = true; + base.CanStop = true; + } + + PerpetuumBootstrapper Bootstrapper { get; set; } + Autofac.IContainer container { get; set; } + IHostStateService hostStateService { get; set; } + + protected override void OnStart(string[] args) + { + ServerStart(); + } + + public void ServerStart() + { + // assumes the server is in the default installation directory. + string gameroot = Properties.Settings.Default.GameRoot; + + try + { + Bootstrapper.Init(gameroot); + } + catch (Exception ex) + { + Logger.Exception(ex); + return; + } + + container = Bootstrapper.GetContainer(); + hostStateService = container.Resolve(); + + Task task = Task.Run((Action)StartServer); + + } + + void StartServer() + { + Bootstrapper.Start(); + Bootstrapper.WaitForStop(); // this blocks ! + base.Stop(); // must call or the service will hang. + } + + void StopServer() + { + // if we are online. stop. + if (hostStateService.State == HostState.Online) + { + // state change from online => stopping + Bootstrapper.Stop(); + } + // wait until we are stopped. (off) + while (hostStateService.State != HostState.Off) + { + // we need to wait for a clean shutdown. Windows... Please wait for us :) + RequestAdditionalTime(10000); // ask for 10 seconds. usually is not required. + } + + Thread.Sleep(10000); // wait 10 seconds for the logging stuff to flush + } + + // SCM stopping or server rebooting (?) + protected override void OnStop() + { + StopServer(); + } + + // this is called when the system is SHUT DOWN. not when it's rebooting ?? + // either way we need to exit cleanly if possible. + protected override void OnShutdown() + { + StopServer(); + } + + } +} diff --git a/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj b/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj new file mode 100644 index 000000000..a226ff8d8 --- /dev/null +++ b/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj @@ -0,0 +1,125 @@ + + + + + Debug + AnyCPU + {9C77CE95-825E-4BB1-9129-66835BDB8C96} + WinExe + Perpetuum.ServerService + Perpetuum.ServerService + v4.6.1 + 512 + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + x64 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Autofac.4.6.1\lib\net45\Autofac.dll + + + + + + + + + + + + + + + + + Component + + + PerpServer.cs + + + Component + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + {E113962C-AD5D-4F17-8D08-656221CBD2D4} + Perpetuum.Bootstrapper + + + {8587A597-848E-497F-8036-3B6F8047EC45} + Perpetuum + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + False + Microsoft .NET Framework 4.6.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/src/Perpetuum.ServerService/Program.cs b/src/Perpetuum.ServerService/Program.cs new file mode 100644 index 000000000..61b7a4a29 --- /dev/null +++ b/src/Perpetuum.ServerService/Program.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration.Install; +using System.Linq; +using System.Reflection; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.ServerService +{ + [RunInstaller(true)] + public class PerpServerInstaller : Installer + { + private ServiceInstaller serviceInstaller1; + private ServiceProcessInstaller processInstaller; + + public PerpServerInstaller() + { + // Instantiate installer for process and service. + processInstaller = new ServiceProcessInstaller(); + serviceInstaller1 = new ServiceInstaller(); + + // The service runs under the system account. + processInstaller.Account = ServiceAccount.LocalSystem; + + // The service is started manually. + serviceInstaller1.StartType = ServiceStartMode.Manual; + + // ServiceName must equal those on ServiceBase derived classes. + serviceInstaller1.ServiceName = "Perpetuum Server"; + + // Add installer to collection. Order is not important if more than one service. + Installers.Add(serviceInstaller1); + Installers.Add(processInstaller); + } + } + + static class Program + { + /// + /// The main entry point for the application. + /// + static void Main(string[] args) + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new PerpServer() + }; + ServiceBase.Run(ServicesToRun); + } + } +} diff --git a/src/Perpetuum.ServerService/Properties/AssemblyInfo.cs b/src/Perpetuum.ServerService/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d18b979fe --- /dev/null +++ b/src/Perpetuum.ServerService/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perpetuum.ServerService")] +[assembly: AssemblyDescription("Perpetuum Windows Server Service")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The OpenPerpetuum Project")] +[assembly: AssemblyProduct("Perpetuum.ServerService")] +[assembly: AssemblyCopyright("Copyright © 2018 The OpenPerpetuum Project")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9c77ce95-825e-4bb1-9129-66835bdb8c96")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Perpetuum.ServerService/Properties/Resources.Designer.cs b/src/Perpetuum.ServerService/Properties/Resources.Designer.cs new file mode 100644 index 000000000..58f9c9d07 --- /dev/null +++ b/src/Perpetuum.ServerService/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Perpetuum.ServerService.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Perpetuum.ServerService.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/Perpetuum.ServerService/Properties/Resources.resx b/src/Perpetuum.ServerService/Properties/Resources.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/src/Perpetuum.ServerService/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Perpetuum.ServerService/Properties/Settings.Designer.cs b/src/Perpetuum.ServerService/Properties/Settings.Designer.cs new file mode 100644 index 000000000..43ce4cc26 --- /dev/null +++ b/src/Perpetuum.ServerService/Properties/Settings.Designer.cs @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Perpetuum.ServerService.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("C:\\PerpetuumServer\\data\\")] + public string GameRoot { + get { + return ((string)(this["GameRoot"])); + } + } + } +} diff --git a/src/Perpetuum.ServerService/Properties/Settings.settings b/src/Perpetuum.ServerService/Properties/Settings.settings new file mode 100644 index 000000000..ae61c7ea2 --- /dev/null +++ b/src/Perpetuum.ServerService/Properties/Settings.settings @@ -0,0 +1,9 @@ + + + + + + C:\PerpetuumServer\data\ + + + \ No newline at end of file diff --git a/src/Perpetuum.ServerService/packages.config b/src/Perpetuum.ServerService/packages.config new file mode 100644 index 000000000..1903d4393 --- /dev/null +++ b/src/Perpetuum.ServerService/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Perpetuum/Accounting/Account.cs b/src/Perpetuum/Accounting/Account.cs index 780cea020..4df1cbbf8 100644 --- a/src/Perpetuum/Accounting/Account.cs +++ b/src/Perpetuum/Accounting/Account.cs @@ -1,3 +1,4 @@ +using Perpetuum.Data; using System; using System.Collections.Generic; @@ -40,6 +41,7 @@ public IDictionary ToDictionary() {k.isEarlyAccess,false}, {k.validUntil, ValidUntil}, {k.email,Email}, + {k.emailConfirmed, EmailConfirmed }, {k.credit,Credit}, {"twitchAuthToken",TwitchAuthToken}, {k.banLength, (int)BanLength.TotalSeconds }, @@ -49,5 +51,17 @@ public IDictionary ToDictionary() return dictionary; } + + /// + /// Force confirmation of an email address in the database. + /// + public void ForceConfirmEmail() + { + Db.Query().CommandText("UPDATE accounts SET EmailConfirmed = 1 WHERE accountID = @id") + .SetParameter("@id", this.Id) + .ExecuteNonQuery(); + + this.EmailConfirmed = true; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 77c22aff0..bc2548ba3 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -1,56 +1,66 @@ using System; using System.Collections.Generic; +using System.Data.SqlClient; using System.Linq; using System.Transactions; using Perpetuum.Accounting.Characters; using Perpetuum.Data; using Perpetuum.Log; +using Perpetuum.Services.EventServices; using Perpetuum.Services.ExtensionService; namespace Perpetuum.Accounting { public class AccountManager : IAccountManager { - private readonly AccountTransactionLogger _transactionLogger; - private readonly AccountWalletFactory _walletFactory; - private readonly EpForActivityLogger _epForActivityLogger; + private const double BOOSTMULTIPLIERMAX = 25; + private const double SERVER_DESIRED_EP_LEVEL = 1000000; + private const double GAURANTEED_BOOST_MAX_THRESH = 45000; + private readonly AccountTransactionLogger transactionLogger; + private readonly AccountWalletFactory walletFactory; + private readonly EpForActivityLogger epForActivityLogger; + private readonly EPBonusEventService epBonusEventService; public AccountManager(IAccountRepository accountRepository, AccountTransactionLogger transactionLogger, AccountWalletFactory walletFactory, - EpForActivityLogger epForActivityLogger) + EpForActivityLogger epForActivityLogger, + EPBonusEventService epBonusEventService) { Repository = accountRepository; - _transactionLogger = transactionLogger; - _walletFactory = walletFactory; - _epForActivityLogger = epForActivityLogger; + this.transactionLogger = transactionLogger; + this.walletFactory = walletFactory; + this.epForActivityLogger = epForActivityLogger; + this.epBonusEventService = epBonusEventService; } public IAccountRepository Repository { get; } - public IAccountWallet GetWallet(Account account,AccountTransactionType transactionType) + public IAccountWallet GetWallet(Account account, AccountTransactionType transactionType) { - return _walletFactory(account,transactionType); + return walletFactory(account, transactionType); } public void LogTransaction(AccountTransactionLogEvent e) { - _transactionLogger.Log(e); + transactionLogger.Log(e); } - public IEnumerable GetTransactionHistory(Account account,TimeSpan offset,TimeSpan length) + public IEnumerable GetTransactionHistory(Account account, TimeSpan offset, TimeSpan length) { var later = DateTime.Now - offset; var earlier = later - length; - return Db.Query().CommandText("select * from accounttransactionlog where accountId = @accountId and created between @earlier and @later and creditchange != 0") - .SetParameter("@accountId",account.Id) - .SetParameter("@earlier",earlier) - .SetParameter("@later",later) + return Db.Query() + .CommandText("select * from accounttransactionlog where accountId = @accountId and created between @earlier and @later and creditchange != 0") + .SetParameter("@accountId", account.Id) + .SetParameter("@earlier", earlier) + .SetParameter("@later", later) .Execute().Select(r => { var transactionType = (AccountTransactionType)r.GetValue("transactionType"); - return new AccountTransactionLogEvent(account,transactionType) + + return new AccountTransactionLogEvent(account, transactionType) { Definition = r.GetValue("definition"), Quantity = r.GetValue("quantity"), @@ -66,25 +76,29 @@ public int GetActiveCharactersCount(Account account) { return Db.Query() .CommandText("select count(*) from characters where accountid=@accountID and active=1") - .SetParameter("@accountID",account.Id) + .SetParameter("@accountID", account.Id) .ExecuteScalar(); } public Character[] GetDeletedCharacters(Account account) { - return Db.Query().CommandText("select characterid from characters where accountid=@accountID and deletedat is not null and active=0") - .SetParameter("@accountID",account.Id) + return Db.Query() + .CommandText("select characterid from characters where accountid=@accountID and deletedat is not null and active=0") + .SetParameter("@accountID", account.Id) .Execute() .Select(r => Character.Get(r.GetValue(0))).ToArray(); } - private int GetLockedEpByCharacters(Account account,IList characters) + private int GetLockedEpByCharacters(Account account, IList characters) { if (characters.Count <= 0) + { return 0; + } - var lockedEp = Db.Query().CommandText($"SELECT COALESCE(SUM(points),0) FROM dbo.accountextensionspent WHERE characterid IN ( {characters.Select(c => c.Id).ArrayToString()} ) AND accountid=@accountID") - .SetParameter("@accountID",account.Id) + var lockedEp = Db.Query() + .CommandText($"SELECT COALESCE(SUM(points),0) FROM dbo.accountextensionspent WHERE characterid IN ( {characters.Select(c => c.Id).ArrayToString()} ) AND accountid=@accountID") + .SetParameter("@accountID", account.Id) .ExecuteScalar(); return lockedEp; @@ -93,29 +107,30 @@ private int GetLockedEpByCharacters(Account account,IList characters) public int GetLockedEpByAccount(Account account) { var deletedCharacters = GetDeletedCharacters(account); + if (deletedCharacters.Length <= 0) + { return 0; + } + + var lockedEp = GetLockedEpByCharacters(account, deletedCharacters); - var lockedEp = GetLockedEpByCharacters(account,deletedCharacters); return lockedEp; } - private const double BOOSTMULTIPLIERMAX = 50; - private const double BOOSTEXPONENT = 2.0; - private const double SERVER_DESIRED_EP_LEVEL = 3000000; - - public IDictionary GetEPData(Account account,Character character) + public IDictionary GetEPData(Account account, Character character) { var availablePoints = CalculateCurrentEp(account); - var totalEPSpentByCharacter = GetSumExtensionPointsSpentByCharacter(account,character); + var totalEPSpentByCharacter = GetSumExtensionPointsSpentByCharacter(account, character); var spentEPPerAccount = GetSumExtensionPointsSpent(account); var lockedEPPerAccount = GetLockedEpByAccount(account); var epCollected = GetExtensionPointsCollected(account); - var boostFactor = GetExperienceBoostingFactor(epCollected,SERVER_DESIRED_EP_LEVEL); - var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor)); + var boostFactor = GetExperienceBoostingFactor(epCollected, SERVER_DESIRED_EP_LEVEL); + var onePointRate = + CalculateBoostedExtensionPoint(1, GetBoostMultiplier(boostFactor, GetEpBonusFromEvent(), GetEpBonusFromSubscription(account))); onePointRate = account.IsDailyEpBoosted ? onePointRate * 2 : onePointRate; - var result = new Dictionary + var result = new Dictionary { {k.points, availablePoints}, {"spentEpPerCharacter", totalEPSpentByCharacter}, @@ -130,38 +145,13 @@ public IDictionary GetEPData(Account account,Character character) return result; } - private static double GetBoostMultiplier(double boostFactor) - { - return (BOOSTMULTIPLIERMAX - 1) * boostFactor; - } - - private static double GetExperienceBoostingFactor(int collectedEpSum,double epLevelThreshold) - { - var linearRatio = collectedEpSum / epLevelThreshold; - var result = 1.0 - (Math.Pow(linearRatio,BOOSTEXPONENT)); - result = result.Clamp(); - - return result; - } - - /// - /// boostMultiplier: 0 .... BOOSTMULTIPLIERMAX ==> usage: inPoint * ( 1 + mult ) - /// - /// - /// - /// - private static int CalculateBoostedExtensionPoint(int inExtensionPoints,double boostMultiplier) - { - var bp = Math.Round(Math.Ceiling(inExtensionPoints * (1 + boostMultiplier))); - return (int)bp; - } - public int CalculateCurrentEp(Account account) { var dailySum = GetDailyPointsSum(account); var penaltySum = GetPenaltyPointsSum(account); var ingameSpent = GetSumExtensionPointsSpent(account); var resultEp = dailySum - penaltySum - ingameSpent; + return resultEp; } @@ -171,8 +161,9 @@ public int CalculateCurrentEp(Account account) /// public int GetDailyPointsSum(Account account) { - var result = Db.Query().CommandText("SELECT SUM(points) FROM extensionpoints WHERE accountid=@accountID") - .SetParameter("@accountID",account.Id) + var result = Db.Query() + .CommandText("SELECT SUM(points) FROM extensionpoints WHERE accountid=@accountID") + .SetParameter("@accountID", account.Id) .ExecuteScalar(); return result ?? 0; @@ -184,8 +175,9 @@ public int GetDailyPointsSum(Account account) /// public int GetPenaltyPointsSum(Account account) { - var result = Db.Query().CommandText("select sum(points) from extensionpointpenalty where accountid=@accountID") - .SetParameter("@accountID",account.Id) + var result = Db.Query() + .CommandText("select sum(points) from extensionpointpenalty where accountid=@accountID") + .SetParameter("@accountID", account.Id) .ExecuteScalar(); return result ?? 0; @@ -194,11 +186,12 @@ public int GetPenaltyPointsSum(Account account) /// /// sum of the extension points the account spent on this character /// - public int GetSumExtensionPointsSpentByCharacter(Account account,Character character) + public int GetSumExtensionPointsSpentByCharacter(Account account, Character character) { - return Db.Query().CommandText("select sum(points) from accountextensionspent WHERE accountID=@accountID and characterid=@characterID") - .SetParameter("@accountID",account.Id) - .SetParameter("@characterID",character.Id) + return Db.Query() + .CommandText("select sum(points) from accountextensionspent WHERE accountID=@accountID and characterid=@characterID") + .SetParameter("@accountID", account.Id) + .SetParameter("@characterID", character.Id) .ExecuteScalar(); } @@ -208,21 +201,24 @@ public int GetSumExtensionPointsSpentByCharacter(Account account,Character chara /// public int GetSumExtensionPointsSpent(Account account) { - var spentSum = Db.Query().CommandText("SELECT SUM(points) FROM accountextensionspent WHERE accountID=@accountID") - .SetParameter("@accountID",account.Id) + var spentSum = Db.Query() + .CommandText("SELECT SUM(points) FROM accountextensionspent WHERE accountID=@accountID") + .SetParameter("@accountID", account.Id) .ExecuteScalar(); + return spentSum; } public int GetExtensionPointsCollected(Account account) { var collectedEp = Db.Query().CommandText("SELECT dbo.extensionPointsCollected(@accountID)") - .SetParameter("@accountID",account.Id) + .SetParameter("@accountID", account.Id) .ExecuteScalar(); + return collectedEp; } - public void FreeLockedEp(Account account,int amount) + public void FreeLockedEp(Account account, int amount) { var deletedCharacters = GetDeletedCharacters(account).Select(c => c.Id).ToList(); @@ -230,17 +226,22 @@ public void FreeLockedEp(Account account,int amount) var pointsToDelete = amount; if (deletedCharacters.Count <= 0) + { return; + } - var entries = Db.Query().CommandText($"select id,points from accountextensionspent where accountid=@accountID and characterid in ({deletedCharacters.ArrayToString()})") - .SetParameter("@accountID",account.Id) - .Execute().Select(r => new KeyValuePair(r.GetValue(0),r.GetValue(1))) + var entries = Db.Query() + .CommandText($"select id,points from accountextensionspent where accountid=@accountID and characterid in ({deletedCharacters.ArrayToString()})") + .SetParameter("@accountID", account.Id) + .Execute().Select(r => new KeyValuePair(r.GetValue(0), r.GetValue(1))) .ToList(); var allLocked = entries.Sum(l => l.Value); + if (allLocked < amount) { throw new PerpetuumException(ErrorCodes.InputTooHigh); } + foreach (var keyValuePair in entries) { var id = keyValuePair.Key; @@ -251,8 +252,8 @@ public void FreeLockedEp(Account account,int amount) //update and exit loop Db.Query().CommandText("update accountextensionspent set points=@newPoints where id=@id") - .SetParameter("@id",id) - .SetParameter("@newPoints",points - pointsToDelete) + .SetParameter("@id", id) + .SetParameter("@newPoints", points - pointsToDelete) .ExecuteNonQuery(); break; @@ -275,105 +276,119 @@ public void FreeLockedEp(Account account,int amount) if (deleteIds.Count > 0) { - Db.Query().CommandText($"delete accountextensionspent where id in ({deleteIds.ArrayToString()})").ExecuteNonQuery(); + Db.Query() + .CommandText($"delete accountextensionspent where id in ({deleteIds.ArrayToString()})") + .ExecuteNonQuery(); } } - /// /// Logs the extension remove actions, not used in game mechanics /// - public void InsertExtensionRemoveLog(Account account,Character character,int extensionId,int extensionLevel,int points) + public void InsertExtensionRemoveLog(Account account, Character character, int extensionId, int extensionLevel, int points) { - const string sqlInsertCommand = @"insert extensionremovelog - (accountid,characterid,extensionid,extensionlevel,points) values - (@accountID,@characterID,@extensionID,@extensionLevel,@points)"; + const string sqlInsertCommand = + @"insert extensionremovelog + (accountid,characterid,extensionid,extensionlevel,points) values + (@accountID,@characterID,@extensionID,@extensionLevel,@points)"; - Db.Query().CommandText(sqlInsertCommand) - .SetParameter("@accountID",account.Id) - .SetParameter("@characterID",character.Id) - .SetParameter("@extensionID",extensionId) - .SetParameter("@extensionLevel",extensionLevel) - .SetParameter("@points",points) - .ExecuteNonQuery().ThrowIfEqual(0,ErrorCodes.SQLInsertError); + Db.Query() + .CommandText(sqlInsertCommand) + .SetParameter("@accountID", account.Id) + .SetParameter("@characterID", character.Id) + .SetParameter("@extensionID", extensionId) + .SetParameter("@extensionLevel", extensionLevel) + .SetParameter("@points", points) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); } /// /// the account bought extensions ingame -> insert record /// - public void AddExtensionPointsSpent(Account account,Character character,int spentPoints,int extensionID,int extensionLevel) + public void AddExtensionPointsSpent(Account account, Character character, int spentPoints, int extensionID, int extensionLevel) { - Db.Query().CommandText("insert accountextensionspent (accountid, points, extensionID, extensionlevel, characterID) values (@accountID, @points,@extensionID,@extensionLevel,@characterID)") - .SetParameter("@accountID",account.Id) - .SetParameter("@points",spentPoints) - .SetParameter("@extensionID",extensionID) - .SetParameter("@extensionLevel",extensionLevel) - .SetParameter("@characterID",character.Id) - .ExecuteNonQuery().ThrowIfEqual(0,ErrorCodes.SQLInsertError); + Db.Query() + .CommandText("insert accountextensionspent (accountid, points, extensionID, extensionlevel, characterID) values (@accountID, @points,@extensionID,@extensionLevel,@characterID)") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", spentPoints) + .SetParameter("@extensionID", extensionID) + .SetParameter("@extensionLevel", extensionLevel) + .SetParameter("@characterID", character.Id) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); } /// /// Starts the subscription period /// - public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTime endTime) - { - Db.Query().CommandText("extensionSubscriptionStart") - .SetParameter("@accountID",account.Id) - .SetParameter("@startTime",startTime) - .SetParameter("@endTime",endTime) - .ExecuteNonQuery(); - } - - public void ExtensionSubscriptionExtend(Account account,DateTime extendedValidUntil) + public void ExtensionSubscriptionStart(Account account, DateTime startTime, DateTime endTime, int multiplierBonus) { - Db.Query().CommandText("extensionSubscriptionExtend") - .SetParameter("@accountID",account.Id) - .SetParameter("@endTime",extendedValidUntil) - .ExecuteNonQuery(); + try + { + Db.Query() + .CommandText("opp.extensionSubscriptionStart") + .SetParameter("@accountID", account.Id) + .SetParameter("@startTime", startTime) + .SetParameter("@endTime", endTime) + .SetParameter("@multiplierBonus", multiplierBonus) + .ExecuteNonQuery(); + } + catch (SqlException exc) + { + exc.Number.ThrowIfEqual(100000, ErrorCodes.ItemNotUsable); + } } - /// - /// penalty IS ++++ positive - /// injection IS ---- negative - /// - public void InsertPenaltyPoint(Account account,AccountExtensionPenaltyType penaltyType,int points,bool forever) + public void InsertPenaltyPoint(Account account, AccountExtensionPenaltyType penaltyType, int points, bool forever) { var affected = - Db.Query().CommandText("INSERT dbo.extensionpointpenalty ( accountid, points, penaltytype, forever ) VALUES ( @accountID,@points,@penaltyType,@forever )") - .SetParameter("@accountID",account.Id) - .SetParameter("@points",points) - .SetParameter("@penaltyType",(int)penaltyType) - .SetParameter("@forever",forever) + Db.Query() + .CommandText("INSERT dbo.extensionpointpenalty ( accountid, points, penaltytype, forever ) VALUES ( @accountID,@points,@penaltyType,@forever )") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", points) + .SetParameter("@penaltyType", (int)penaltyType) + .SetParameter("@forever", forever) .ExecuteNonQuery(); (affected == 1).ThrowIfFalse(ErrorCodes.SQLInsertError); } - public int AddExtensionPointsBoostAndLog(Account account,Character character,EpForActivityType activityType,int points) + public int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType, int points) { if (points <= 0) + { return 0; + } + var bonusIncrease = GetEpBonusFromEvent(); + var itemIncrease = GetEpBonusFromSubscription(account); var rawPoints = points; - var boostFactor = GetExperienceBoostingFactor(GetExtensionPointsCollected(account),SERVER_DESIRED_EP_LEVEL); - var boostedPoints = GetBoostedExtensionPoints(account,points); + var boostFactor = GetExperienceBoostingFactor(GetExtensionPointsCollected(account), SERVER_DESIRED_EP_LEVEL); + var boostedPoints = GetBoostedExtensionPoints(account, points, bonusIncrease, itemIncrease); Transaction.Current.OnCommited(() => { - LogEpForActivity(account,character,activityType,rawPoints,boostedPoints,boostFactor); + LogEpForActivity( + account, + character, + activityType, + rawPoints, + boostedPoints, + boostFactor, + (int)BOOSTMULTIPLIERMAX, + (int)bonusIncrease); }); - AddExtensionPoints(account,boostedPoints); + AddExtensionPoints(account, boostedPoints); return boostedPoints; } - public void AddExtensionPoints(Account account,int pointsToInject) + public void AddExtensionPoints(Account account, int pointsToInject) { if (pointsToInject <= 0) return; - InjectExtensionPoints(account,pointsToInject); + InjectExtensionPoints(account, pointsToInject); Transaction.Current.OnCommited(() => { @@ -381,11 +396,104 @@ public void AddExtensionPoints(Account account,int pointsToInject) }); } - public void InjectExtensionPoints(Account account,int points) + public IEnumerable GetEpForActivityHistory(Account account, DateTime earlier, DateTime later) + { + return Db.Query() + .CommandText("select * from epforactivitylog where accountId = @accountId and eventtime between @earlier and @later") + .SetParameter("@accountId", account.Id) + .SetParameter("@earlier", earlier) + .SetParameter("@later", later) + .Execute().Select(r => + { + var transactionType = (EpForActivityType)r.GetValue("epforactivitytype"); + + return new EpForActivityLogEvent(transactionType) + { + CharacterId = r.GetValue("characterid"), + RawPoints = r.GetValue("rawpoints"), + Points = r.GetValue("points"), + BoostFactor = r.GetValue("boostfactor"), + BoostMultiplier = r.GetValue(k.multiplier), + Created = r.GetValue("eventtime") + }; + }).ToArray(); + } + + public void PackageGenerateAll(Account account) + { + // generaljunk neki josagokat + Db.Query() + .CommandText("accountPackageGenerateAll") + .SetParameter("@accountId", account.Id) + .ExecuteNonQuery(); + } + + + /// + /// Computes the final EPMultiplier for an account. + /// + /// Normalized value mapping AccountEP from 0->SERVER_DESIRED_EP_LEVEL as [1.0->0.0] + /// A boostfactor-agnostic multiplier -- usually from EPBonusEvents + /// EP Multiplier + private static double GetBoostMultiplier(double boostFactor, double bonusIncrease, int itemIncrease) + { + return ((BOOSTMULTIPLIERMAX - 1) * boostFactor) + bonusIncrease + itemIncrease; + } + + private static double GetExperienceBoostingFactor(int collectedEpSum, double epLevelThreshold) + { + if (collectedEpSum < GAURANTEED_BOOST_MAX_THRESH) + { + return 1.0; + } + + var linearRatio = collectedEpSum / epLevelThreshold; + var result = 1.0 - linearRatio; + result = result.Clamp(); + + return result; + } + + /// + /// boostMultiplier: 0 .... BOOSTMULTIPLIERMAX ==> usage: inPoint * ( 1 + mult ) + /// + /// + /// + /// + private static int CalculateBoostedExtensionPoint(int inExtensionPoints, double boostMultiplier) + { + var bp = Math.Round(Math.Ceiling(inExtensionPoints * (1 + boostMultiplier))); + + return (int)bp; + } + + private double GetEpBonusFromEvent() + { + return epBonusEventService.GetBonus(); + } + + private int GetEpBonusFromSubscription(Account account) + { + var dataRecord = Db.Query() + .CommandText("opp.getExtensionSubscription") + .SetParameter("@accountID", account.Id) + .Execute(); + + if (!dataRecord.Any()) + return 0; + + var record = dataRecord.SingleOrDefault(); + int ord = record.GetOrdinal("multiplierBonus"); + + return record.GetInt32(ord); + } + + private void InjectExtensionPoints(Account account, int points) { - Db.Query().CommandText("extensionPointsInject") - .SetParameter("@accountID",account.Id) - .SetParameter("@points",points) + Db.Query() + .CommandText("extensionPointsInject") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", points) .ExecuteNonQuery(); } @@ -394,13 +502,16 @@ public void InjectExtensionPoints(Account account,int points) /// Returns the boosted points /// /// - private int GetBoostedExtensionPoints(Account account,int points) + private int GetBoostedExtensionPoints(Account account, int points, double bonusIncrease, int itemIncrease) { // >>>>>>>>>> ami ebbol kijon az injektolhato <<<<<<<<<<< var dailyPointsSum = GetExtensionPointsCollected(account); - var realEpGain = AddExtensionPointWithBoosting(points,dailyPointsSum,SERVER_DESIRED_EP_LEVEL); + var realEpGain = AddExtensionPointWithBoosting(points, dailyPointsSum, SERVER_DESIRED_EP_LEVEL, bonusIncrease, itemIncrease); if (account.IsDailyEpBoosted) + { realEpGain *= 2; + } + return realEpGain; } @@ -410,37 +521,39 @@ private int GetBoostedExtensionPoints(Account account,int points) /// /// /// + /// Any multiplier increase over the BOOSTMULTIPLIERMAX /// - private static int AddExtensionPointWithBoosting(int extensionPointsToAdd,int accoutEpSum,double epLevelThreshold) + private static int AddExtensionPointWithBoosting( + int extensionPointsToAdd, + int accoutEpSum, + double epLevelThreshold, + double bonusIncrease, + int itemIncrease) { - //above threshold -> no change - if (accoutEpSum >= epLevelThreshold) - return extensionPointsToAdd; - var result = 0; var currentEp = accoutEpSum; - //var builder = new StringBuilder(); - for (var i = 0;i < extensionPointsToAdd;i++) + for (var i = 0; i < extensionPointsToAdd; i++) { - var expBoostingFactor = GetExperienceBoostingFactor(currentEp,epLevelThreshold); - var boostingMultiplier = GetBoostMultiplier(expBoostingFactor); - var pointsToAdd = CalculateBoostedExtensionPoint(1,boostingMultiplier); + var expBoostingFactor = GetExperienceBoostingFactor(currentEp, epLevelThreshold); + var boostingMultiplier = GetBoostMultiplier(expBoostingFactor, bonusIncrease, itemIncrease); + var pointsToAdd = CalculateBoostedExtensionPoint(1, boostingMultiplier); result += pointsToAdd; currentEp += pointsToAdd; - - //builder.AppendLine(); - //builder.Append($"BoostFactor:{Math.Round(expBoostingFactor,4)}"); - //builder.Append($" rate: 1:{pointsToAdd}"); - //builder.Append($" tcurrentEP:{currentEp}"); } - //Logger.Warning(builder.ToString()); - - return Math.Max(result,extensionPointsToAdd); + return Math.Max(result, extensionPointsToAdd); } - private void LogEpForActivity(Account account,Character character,EpForActivityType activityType,int rawPoints,int points,double boostFactor) + private void LogEpForActivity( + Account account, + Character character, + EpForActivityType activityType, + int rawPoints, + int points, + double boostFactor, + int bonusMultiplier, + int additionalMultiplier) { var epForActivityLogEvent = new EpForActivityLogEvent(activityType) { @@ -449,37 +562,18 @@ private void LogEpForActivity(Account account,Character character,EpForActivityT RawPoints = rawPoints, Points = points, BoostFactor = boostFactor, + BoostMultiplier = bonusMultiplier, + EventMultiplier = additionalMultiplier, }; - _epForActivityLogger.Log(epForActivityLogEvent); - Logger.Info($"EP4Activity:{activityType} accountId:{account.Id} characterId:{character.Id} raw:{rawPoints} pts:{points} boostFactor:{Math.Round(boostFactor,4)}"); + epForActivityLogger.Log(epForActivityLogEvent); + Logger.Info($"EP4Activity:{activityType} accountId:{account.Id} characterId:{character.Id} raw:{rawPoints} pts:{points} boostFactor:{Math.Round(boostFactor, 4)} bonusMultiplier:{bonusMultiplier} additionalMultiplier:{additionalMultiplier}"); } - public IEnumerable GetEpForActivityHistory(Account account,DateTime earlier,DateTime later) + /// + public void UnlockEpAndReset(Account account, Character character) { - return Db.Query().CommandText("select * from epforactivitylog where accountId = @accountId and eventtime between @earlier and @later") - .SetParameter("@accountId",account.Id) - .SetParameter("@earlier",earlier) - .SetParameter("@later",later) - .Execute().Select(r => - { - var transactionType = (EpForActivityType)r.GetValue("epforactivitytype"); - return new EpForActivityLogEvent(transactionType) - { - CharacterId = r.GetValue("characterid"), - RawPoints = r.GetValue("rawpoints"), - Points = r.GetValue("points"), - BoostFactor = r.GetValue("boostfactor"), - Created = r.GetValue("eventtime") - }; - }).ToArray(); - } - - - public void PackageGenerateAll(Account account) - { - // generaljunk neki josagokat - Db.Query().CommandText("accountPackageGenerateAll").SetParameter("@accountId",account.Id).ExecuteNonQuery(); + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/Perpetuum/Accounting/AccountRepository.cs b/src/Perpetuum/Accounting/AccountRepository.cs index 48d232c46..28c573d4d 100644 --- a/src/Perpetuum/Accounting/AccountRepository.cs +++ b/src/Perpetuum/Accounting/AccountRepository.cs @@ -116,6 +116,15 @@ public Account Get(int accountId, string steamId) return CreateAccountFromRecord(record); } + public Account Get(string email) + { + var record = Db.Query("select * from accounts where email = @email") + .SetParameter("email", email) + .ExecuteSingleRow(); + + return CreateAccountFromRecord(record); + } + public Account Get(string email, string password) { var record = Db.Query("select * from accounts where email = @email and password = @password") diff --git a/src/Perpetuum/Accounting/Characters/Character.cs b/src/Perpetuum/Accounting/Characters/Character.cs index cc71d65ee..178baac67 100644 --- a/src/Perpetuum/Accounting/Characters/Character.cs +++ b/src/Perpetuum/Accounting/Characters/Character.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.Caching; -using System.Threading.Tasks; -using System.Transactions; using Perpetuum.Common.Loggers.Transaction; using Perpetuum.Containers; using Perpetuum.Data; @@ -23,17 +16,29 @@ using Perpetuum.Units.DockingBases; using Perpetuum.Wallets; using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Caching; +using System.Threading.Tasks; +using System.Transactions; namespace Perpetuum.Accounting.Characters { public delegate Character CharacterFactory(int id); - public class Character : IEquatable,IComparable + public class Character : IEquatable, IComparable { private static Character _none; public static Character None => _none ?? (_none = CharacterFactory(0)); + public static bool IsSystemCharacter(Character c) + { + return c.Nick.Contains("[OPP]"); //TODO better configuration of system characters to avoid flimsy name rule + } + private readonly IAccountManager _accountManager; private readonly Lazy _zoneManager; private readonly DockingBaseHelper _dockingBaseHelper; @@ -82,17 +87,18 @@ public class Character : IEquatable,IComparable private const string CACHE_KEY_ID_TO_EID = "id_to_eid_"; private const string CACHE_KEY_EID_TO_ID = "eid_to_id_"; private const string CACHE_KEY_ID_TO_ACCOUNTID = "id_to_accountid_"; + private const string LAST_RESPEC = "LastRespec"; public static ObjectCache CharacterCache { get; set; } - + public static CharacterFactory CharacterFactory { get; set; } public Character() { - + } - public Character(int id,IAccountManager accountManager, + public Character(int id, IAccountManager accountManager, Lazy zoneManager, DockingBaseHelper dockingBaseHelper, RobotHelper robotHelper, @@ -119,7 +125,9 @@ public Character(int id,IAccountManager accountManager, _walletHelper = walletHelper; if (id <= 0) + { id = 0; + } Id = id; } @@ -160,7 +168,7 @@ public bool IsOffensiveNick get => ReadValueFromDb(FIELD_OFFENSIVE_NICK); set { - WriteValueToDb(FIELD_OFFENSIVE_NICK,value); + WriteValueToDb(FIELD_OFFENSIVE_NICK, value); WriteValueToDb(FIELD_NICK_CORRECTED, !value); } } @@ -233,6 +241,12 @@ public DateTime LastLogout set => WriteValueToDb(FIELD_LAST_LOGOUT, value); } + public DateTime LastRespec + { + get => ReadValueFromDb(LAST_RESPEC); + set => WriteValueToDb(LAST_RESPEC, value); + } + public DateTime LastUsed { set => WriteValueToDb(FIELD_LAST_USED, value); @@ -273,13 +287,10 @@ public Position? ZonePosition { get { - var x = ReadValueFromDb(FIELD_POSITION_X); - var y = ReadValueFromDb(FIELD_POSITION_Y); + double? x = ReadValueFromDb(FIELD_POSITION_X); + double? y = ReadValueFromDb(FIELD_POSITION_Y); - if (x == null || y == null) - return null; - - return new Position((double) x,(double) y); + return x == null || y == null ? null : (Position?)new Position((double)x, (double)y); } set { @@ -288,13 +299,13 @@ public Position? ZonePosition if (value != null) { - var p = (Position) value; + Position p = (Position)value; x = p.X; y = p.Y; } - WriteValueToDb(FIELD_POSITION_X,x); - WriteValueToDb(FIELD_POSITION_Y,y); + WriteValueToDb(FIELD_POSITION_X, x); + WriteValueToDb(FIELD_POSITION_Y, y); } } @@ -305,7 +316,7 @@ public long CorporationEid { WriteValueToDb(FIELD_CORPORATION_EID, value); - Db.Query().CommandText("update entities set parent=@corporationEID where eid=@characterEID") + _ = Db.Query().CommandText("update entities set parent=@corporationEID where eid=@characterEID") .SetParameter("@corporationEID", value) .SetParameter("@characterEID", Eid) .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); @@ -315,25 +326,25 @@ public long CorporationEid public long DefaultCorporationEid { get => ReadValueFromDb(FIELD_DEFAULT_CORPORATION_EID); - set => WriteValueToDb(FIELD_DEFAULT_CORPORATION_EID,value); + set => WriteValueToDb(FIELD_DEFAULT_CORPORATION_EID, value); } public long AllianceEid { get => ReadValueFromDb(FIELD_ALLIANCE_EID) ?? 0L; - set => WriteValueToDb(FIELD_ALLIANCE_EID,value == 0L ? (object) null : value); + set => WriteValueToDb(FIELD_ALLIANCE_EID, value == 0L ? (object)null : value); } public long ActiveRobotEid { get => ReadValueFromDb(FIELD_ACTIVE_CHASSIS) ?? 0; - set => WriteValueToDb(FIELD_ACTIVE_CHASSIS,value == 0L ? (object)null : value); + set => WriteValueToDb(FIELD_ACTIVE_CHASSIS, value == 0L ? (object)null : value); } public long CurrentDockingBaseEid { get => ReadValueFromDb(FIELD_BASE_EID); - set => WriteValueToDb(FIELD_BASE_EID,value); + set => WriteValueToDb(FIELD_BASE_EID, value); } public DateTime NextAvailableUndockTime @@ -365,24 +376,29 @@ public bool GlobalMuted public long? HomeBaseEid { get => ReadValueFromDb(FIELD_HOME_BASE_EID); - set => WriteValueToDb(FIELD_HOME_BASE_EID,value); + set => WriteValueToDb(FIELD_HOME_BASE_EID, value); } - private static string GetCacheKey(string prefix,object key) + private static string GetCacheKey(string prefix, object key) { return $"{prefix}_{key}"; } public void RemoveFromCache() { - CharacterCache.Remove(GetCacheKey(CACHE_KEY_ID_TO_EID, Id)); - CharacterCache.Remove(GetCacheKey(CACHE_KEY_EID_TO_ID, Eid)); + _ = CharacterCache.Remove(GetCacheKey(CACHE_KEY_ID_TO_EID, Id)); + _ = CharacterCache.Remove(GetCacheKey(CACHE_KEY_EID_TO_ID, Eid)); RemoveAccountIdFromCache(); } + public void UpdateExtensionsCache(CharacterExtensionCollection extensions) + { + CharacterCache.Set(Id.ToString(), extensions, new TimeSpan(0, 1, 0)); + } + private void RemoveAccountIdFromCache() { - CharacterCache.Remove(GetCacheKey(CACHE_KEY_ID_TO_ACCOUNTID, Id)); + _ = CharacterCache.Remove(GetCacheKey(CACHE_KEY_ID_TO_ACCOUNTID, Id)); } public override int GetHashCode() @@ -394,31 +410,21 @@ public int CompareTo(Character other) { return Id.CompareTo(other?.Id); } - + public override bool Equals(object obj) { - var other = obj as Character; + Character other = obj as Character; return other != Character.None && Equals(other); } public bool Equals(Character other) { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - return Id == other.Id; + return !(other is null) && (ReferenceEquals(this, other) || Id == other.Id); } public static bool operator ==(Character left, Character right) { - if (ReferenceEquals(left, right)) - return true; - - if ((object)left == null || (object)right == null) - return false; - - return left.Equals(right); + return ReferenceEquals(left, right) || (left is object && right is object && left.Equals(right)); } public static bool operator !=(Character left, Character right) @@ -438,7 +444,7 @@ private string CreateCacheKey(string key) private T GetValueFromCache(string key) { - return CharacterCache.Get(CreateCacheKey(key),() => default(T)); + return CharacterCache.Get(CreateCacheKey(key), () => default(T)); } private void SetValueToCache(string key, T value) @@ -460,28 +466,28 @@ private void WriteValueToDb(string name, object value) private static T ReadValueFromDb(int id, string name) { - if (id == 0) - return default(T); - - return Db.Query().CommandText("select " + name + " from characters where characterid = @id").SetParameter("@id",id).ExecuteScalar(); + return id == 0 + ? default + : Db.Query().CommandText("select " + name + " from characters where characterid = @id").SetParameter("@id", id).ExecuteScalar(); } private static T ReadValueFromDb(long eid, string name) { - if (eid == 0) - return default(T); - - return Db.Query().CommandText("select " + name + " from characters where rooteid = @eid").SetParameter("@eid",eid).ExecuteScalar(); + return eid == 0 + ? default + : Db.Query().CommandText("select " + name + " from characters where rooteid = @eid").SetParameter("@eid", eid).ExecuteScalar(); } private static void WriteValueToDb(int id, string name, object value) { - if ( id == 0 ) + if (id == 0) + { return; + } - Db.Query().CommandText("update characters set " + name + " = @value where characterid = @id") - .SetParameter("@id",id) - .SetParameter("@value",value) + _ = Db.Query().CommandText("update characters set " + name + " = @value where characterid = @id") + .SetParameter("@id", id) + .SetParameter("@value", value) .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); } @@ -489,9 +495,11 @@ private static void WriteValueToDb(int id, string name, object value) public static Character GetByEid(long characterEid) { if (characterEid == 0L) + { return None; + } - var characterId = GetIdByEid(characterEid); + int characterId = GetIdByEid(characterEid); return CharacterFactory(characterId); } @@ -503,23 +511,17 @@ public static Character Get(int id) private static int GetCachedAccountId(int id) { - if (id == 0) - return 0; - - return CharacterCache.Get(GetCacheKey(CACHE_KEY_ID_TO_ACCOUNTID,id), () => ReadValueFromDb(id,FIELD_ACCOUNT_ID)); + return id == 0 ? 0 : CharacterCache.Get(GetCacheKey(CACHE_KEY_ID_TO_ACCOUNTID, id), () => ReadValueFromDb(id, FIELD_ACCOUNT_ID)); } private static long GetEid(int id) { - if (id == 0) - return 0L; - - return CharacterCache.Get(GetCacheKey(CACHE_KEY_ID_TO_EID, id), () => ReadValueFromDb(id, FIELD_ROOT_EID)); + return id == 0 ? 0L : CharacterCache.Get(GetCacheKey(CACHE_KEY_ID_TO_EID, id), () => ReadValueFromDb(id, FIELD_ROOT_EID)); } public static int GetIdByEid(long eid) { - return CharacterCache.Get(GetCacheKey(CACHE_KEY_EID_TO_ID, eid), () => ReadValueFromDb(eid,FIELD_CHARACTER_ID)); + return CharacterCache.Get(GetCacheKey(CACHE_KEY_EID_TO_ID, eid), () => ReadValueFromDb(eid, FIELD_CHARACTER_ID)); } public static bool Exists(int id) @@ -527,11 +529,11 @@ public static bool Exists(int id) return id != 0 && ReadValueFromDb(id, FIELD_CHARACTER_ID) > 0; } - public static void CheckNickAndThrowIfFailed(string nick, AccessLevel accessLevel,Account issuerAccount) + public static void CheckNickAndThrowIfFailed(string nick, AccessLevel accessLevel, Account issuerAccount) { nick = nick.Trim(); - nick.Length.ThrowIfLess(3, ErrorCodes.NickTooShort); - nick.Length.ThrowIfGreater(25, ErrorCodes.NickTooLong); + _ = nick.Length.ThrowIfLess(3, ErrorCodes.NickTooShort); + _ = nick.Length.ThrowIfGreater(25, ErrorCodes.NickTooLong); nick.AllowAscii().ThrowIfFalse(ErrorCodes.OnlyAsciiAllowed); if (!accessLevel.IsAdminOrGm()) @@ -540,7 +542,7 @@ public static void CheckNickAndThrowIfFailed(string nick, AccessLevel accessLeve } //check history - var inHistory = + int inHistory = Db.Query().CommandText("select count(*) from characternickhistory where nick=@nick and accountid != @accountID") .SetParameter("@accountID", issuerAccount.Id) .SetParameter("@nick", nick) @@ -549,9 +551,11 @@ public static void CheckNickAndThrowIfFailed(string nick, AccessLevel accessLeve //is nick belongs to an active of inavtive character - var owner = GetByNick(nick); + Character owner = GetByNick(nick); if (owner == Character.None) + { return; + } // ok, now we know that the nick is used in the characters table, lets check ownership and timeouts! @@ -559,8 +563,8 @@ public static void CheckNickAndThrowIfFailed(string nick, AccessLevel accessLeve owner.IsActive.ThrowIfTrue(ErrorCodes.NickTaken); //if the character is deleted and belongs to the issuer then it's ok - var b = owner.AccountId != issuerAccount.Id; - if (b && !(DateTime.Now.Subtract(owner.DeletedAt).TotalDays > 1)) + bool b = owner.AccountId != issuerAccount.Id; + if (b && (DateTime.Now.Subtract(owner.DeletedAt).TotalDays <= 1)) { throw new PerpetuumException(ErrorCodes.NickTaken); } @@ -574,13 +578,13 @@ public static IEnumerable GetCharactersDockedInBase(long baseEid) { return Db.Query().CommandText("select characterid from characters where docked=1 and baseEID=@baseEID and active=1 and inUse=1") .SetParameter("@baseEID", baseEid) - .Execute().Select(r => Get((int) r.GetValue(0))); + .Execute().Select(r => Get(r.GetValue(0))); } [UsedImplicitly] public static Character GetByNick(string nick) { - var id = Db.Query().CommandText("select characterid from characters where nick = @nick").SetParameter("@nick",nick).ExecuteScalar(); + int id = Db.Query().CommandText("select characterid from characters where nick = @nick").SetParameter("@nick", nick).ExecuteScalar(); return Get(id); } @@ -598,8 +602,8 @@ public void LogTransaction(TransactionLogEvent e) public IDictionary GetTransactionHistory(int offsetInDays) { - var later = DateTime.Now.AddDays(-offsetInDays); - var earlier = later.AddDays(-2); + DateTime later = DateTime.Now.AddDays(-offsetInDays); + DateTime earlier = later.AddDays(-2); const string sqlCmd = @"SELECT transactionType, amount, @@ -611,7 +615,7 @@ public IDictionary GetTransactionHistory(int offsetInDays) FROM charactertransactions WHERE characterid = @characterId AND transactiondate between @earlier AND @later and amount != 0"; - var result = Db.Query().CommandText(sqlCmd) + Dictionary result = Db.Query().CommandText(sqlCmd) .SetParameter("@characterId", Id) .SetParameter("@earlier", earlier) .SetParameter("@later", later) @@ -628,8 +632,8 @@ public bool IsInTraining() public void CheckNextAvailableUndockTimeAndThrowIfFailed() { - var nextAvailableUndockTime = NextAvailableUndockTime; - nextAvailableUndockTime.ThrowIfGreater(DateTime.Now, ErrorCodes.DockingTimerStillRunning, gex => gex.SetData("nextAvailable", nextAvailableUndockTime)); + DateTime nextAvailableUndockTime = NextAvailableUndockTime; + _ = nextAvailableUndockTime.ThrowIfGreater(DateTime.Now, ErrorCodes.DockingTimerStillRunning, gex => gex.SetData("nextAvailable", nextAvailableUndockTime)); } public void SendErrorMessage(Command command, ErrorCodes error) @@ -644,61 +648,67 @@ public MessageBuilder CreateErrorMessage(Command command, ErrorCodes error) public void CheckPrivilegedTransactionsAndThrowIfFailed() { - IsPrivilegedTransactionsAllowed().ThrowIfError(); + _ = IsPrivilegedTransactionsAllowed().ThrowIfError(); } public ErrorCodes IsPrivilegedTransactionsAllowed() { - var accessLevel = AccessLevel; - if (!accessLevel.IsAnyPrivilegeSet()) - return ErrorCodes.NoError; - - if (!accessLevel.IsAdminOrGm()) - return ErrorCodes.AccessDenied; - - return ErrorCodes.NoError; + AccessLevel accessLevel = AccessLevel; + return !accessLevel.IsAnyPrivilegeSet() + ? ErrorCodes.NoError + : !accessLevel.IsAdminOrGm() ? ErrorCodes.AccessDenied : ErrorCodes.NoError; } public bool IsRobotSelectedForOtherCharacter(long robotEid) { - var selectCheck = Db.Query().CommandText("select count(*) from characters where activechassis=@robotEID and characterID != @characterID") + int selectCheck = Db.Query().CommandText("select count(*) from characters where activechassis=@robotEID and characterID != @characterID") .SetParameter("@characterID", Id).SetParameter("@robotEID", robotEid) .ExecuteNonQuery(); if (selectCheck <= 0) + { return false; + } Logger.Error($"An evil attempt to select a robot twice happened. characterID:{Id} robotEID:{robotEid}"); return true; } - public IWallet GetWalletWithAccessCheck(bool useCorporationWallet,TransactionType transactionType,params CorporationRole[] roles) + public IWallet GetWalletWithAccessCheck(bool useCorporationWallet, TransactionType transactionType, params CorporationRole[] roles) { - var thisCharacter = this; - return GetWallet(useCorporationWallet,transactionType,role => + Character thisCharacter = this; + return GetWallet(useCorporationWallet, transactionType, role => { - if (role.IsAnyRole(CorporationRole.CEO,CorporationRole.DeputyCEO,CorporationRole.Accountant)) + if (role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO, CorporationRole.Accountant)) + { return true; + } if (!role.IsAnyRole(CorporationRole.CEO)) - thisCharacter._corporationManager.IsInJoinOrLeave(thisCharacter).ThrowIfError(); + { + _ = thisCharacter._corporationManager.IsInJoinOrLeave(thisCharacter).ThrowIfError(); + } return role.IsAnyRole(roles); }); } - public IWallet GetWallet(bool useCorporationWallet,TransactionType transactionType,Predicate accessChecker = null) + public IWallet GetWallet(bool useCorporationWallet, TransactionType transactionType, Predicate accessChecker = null) { if (!useCorporationWallet) + { return GetWallet(transactionType); + } - var privateCorporation = GetPrivateCorporationOrThrow(); + PrivateCorporation privateCorporation = GetPrivateCorporationOrThrow(); if (accessChecker != null) { - var role = privateCorporation.GetMemberRole(this); + CorporationRole role = privateCorporation.GetMemberRole(this); if (!accessChecker(role)) + { throw new PerpetuumException(ErrorCodes.InsufficientPrivileges); + } } return new CorporationWallet(privateCorporation); @@ -706,22 +716,22 @@ public IWallet GetWallet(bool useCorporationWallet,TransactionType trans public IWallet GetWallet(TransactionType transactionType) { - return _walletHelper.GetWallet(this,transactionType); + return _walletHelper.GetWallet(this, transactionType); } public void TransferCredit(Character target, long amount) { - _walletHelper.TransferCredit(this,target,amount); + _walletHelper.TransferCredit(this, target, amount); } public void AddToWallet(TransactionType transactionType, double amount) { - _walletHelper.AddToWallet(this,transactionType,amount); + _walletHelper.AddToWallet(this, transactionType, amount); } public void SubtractFromWallet(TransactionType transactionType, double amount) { - _walletHelper.SubtractFromWallet(this,transactionType,amount); + _walletHelper.SubtractFromWallet(this, transactionType, amount); } public ICharacterSocial GetSocial() @@ -739,13 +749,17 @@ public void ResetAllExtensions() DeleteAllSpentPoints(); //reset the actual extension levels - Db.Query().CommandText("delete characterextensions where characterid=@characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText( +@"DELETE ce FROM characterextensions ce +INNER JOIN extensions e +ON ce.extensionid = e.extensionid +WHERE characterid=@characterID AND e.active = 1 AND e.hidden = 0") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); //reset remove log - Db.Query().CommandText("delete extensionremovelog where characterid=@characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("DELETE extensionremovelog WHERE characterid=@characterID") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); //remove ram @@ -754,13 +768,13 @@ public void ResetAllExtensions() public void IncreaseExtensionLevel(int extensionId, int extensionLevel) { - Db.Query().CommandText("dbo.increaseExtensionLevel") + _ = Db.Query().CommandText("dbo.increaseExtensionLevel") .SetParameter("@characterID", Id) .SetParameter("@extensionID", extensionId) .SetParameter("@extensionLevel", extensionLevel) .ExecuteScalar().ThrowIfEqual(0, ErrorCodes.SQLExecutionError); - var thisCharacter = this; + Character thisCharacter = this; Transaction.Current.OnCommited(() => thisCharacter._characterExtensions.Remove(thisCharacter)); } @@ -776,13 +790,13 @@ public CharacterExtensionCollection GetExtensions() public void SetAllExtensionLevel(int level) { - var extensions = _extensionReader.GetExtensions().Values.Where(e => !e.hidden).Select(e => new Extension(e.id, level)); + IEnumerable extensions = _extensionReader.GetExtensions().Values.Where(e => !e.hidden).Select(e => new Extension(e.id, level)); SetExtensions(extensions); } public void SetExtensions(IEnumerable extensions) { - foreach (var extension in extensions) + foreach (Extension extension in extensions) { SetExtension(extension); } @@ -798,13 +812,13 @@ public void SetExtension(Extension extension) return; } - Db.Query().CommandText("dbo.setExtensionLevel") + _ = Db.Query().CommandText("dbo.setExtensionLevel") .SetParameter("@characterID", Id) .SetParameter("@extensionID", extension.id) .SetParameter("@extensionLevel", extension.level) .ExecuteScalar().ThrowIfEqual(0, ErrorCodes.SQLExecutionError); - var tmpCharacter = this; + Character tmpCharacter = this; Transaction.Current.OnCommited(() => tmpCharacter._characterExtensions.Remove(tmpCharacter)); } @@ -820,7 +834,7 @@ public double GetExtensionsBonusSummary(params string[] extensionNames) public double GetExtensionsBonusSummary(IEnumerable extensionIDs) { - var thisC = this; + Character thisC = this; return GetExtensions().SelectById(extensionIDs).Sum(e => e.level * thisC._extensionReader.GetExtensionByID(e.id).bonus); } @@ -836,7 +850,7 @@ public double GetExtensionBonus(int extensionId) public int GetExtensionLevelSummaryByName(params string[] extensionNames) { - var ex = GetExtensions(); + CharacterExtensionCollection ex = GetExtensions(); return _extensionReader.GetExtensionIDsByName(extensionNames).Sum(extensionId => ex.GetLevel(extensionId)); } @@ -861,21 +875,23 @@ public bool IsBlocked(Character otherCharacter) } [Pure] - public int AddExtensionPointsBoostAndLog(EpForActivityType activityType, int points) + public int AddExtensionPointsBoostAndLog(EpForActivityType activityType, int points) { if (points <= 0) + { return 0; + } - var account = GetAccount(); + Account account = GetAccount(); Debug.Assert(account != null, "account != null"); - return _accountManager.AddExtensionPointsBoostAndLog(account,this, activityType, points); + return _accountManager.AddExtensionPointsBoostAndLog(account, this, activityType, points); } [CanBeNull] public Task ReloadContainerOnZoneAsync() { - var character = this; + Character character = this; return Task.Run(() => character.ReloadContainerOnZone()); } @@ -894,9 +910,9 @@ public PublicContainer GetPublicContainerWithItems() return GetCurrentDockingBase().GetPublicContainerWithItems(this); } - public void SendItemErrorMessage(Command command,ErrorCodes error,Item item) + public void SendItemErrorMessage(Command command, ErrorCodes error, Item item) { - CreateErrorMessage(command,error) + CreateErrorMessage(command, error) .SetExtraInfo(d => { d["eid"] = item.Eid; @@ -907,25 +923,25 @@ public void SendItemErrorMessage(Command command,ErrorCodes error,Item item) public bool TechTreeNodeUnlocked(int definition) { if (_techTreeService.GetUnlockedNodes(Eid).Any(n => n.Definition == definition)) + { return true; + } - var hasRole = Corporation.GetRoleFromSql(this).HasRole(PresetCorporationRoles.CAN_LIST_TECHTREE); + bool hasRole = Corporation.GetRoleFromSql(this).HasRole(PresetCorporationRoles.CAN_LIST_TECHTREE); return hasRole && _techTreeService.GetUnlockedNodes(CorporationEid).Any(n => n.Definition == definition); } public bool HasTechTreeBonus(int definition) { - var hasRole = Corporation.GetRoleFromSql(this).HasRole(PresetCorporationRoles.CAN_LIST_TECHTREE); - if (!hasRole) - return false; - - return _techTreeService.GetUnlockedNodes(Eid).Any(n => n.Definition == definition) && (_techTreeService.GetUnlockedNodes(CorporationEid).Any(n => n.Definition == definition)); + bool hasRole = Corporation.GetRoleFromSql(this).HasRole(PresetCorporationRoles.CAN_LIST_TECHTREE); + return hasRole +&& _techTreeService.GetUnlockedNodes(Eid).Any(n => n.Definition == definition) && _techTreeService.GetUnlockedNodes(CorporationEid).Any(n => n.Definition == definition); } public DockingBase GetHomeBaseOrCurrentBase() { - var resultBaseEid = HomeBaseEid ?? 0L; - var wasHomeBase = true; + long resultBaseEid = HomeBaseEid ?? 0L; + bool wasHomeBase = true; if (resultBaseEid == 0) { @@ -933,9 +949,11 @@ public DockingBase GetHomeBaseOrCurrentBase() wasHomeBase = false; } - var dockingBase = _dockingBaseHelper.GetDockingBase(resultBaseEid); + DockingBase dockingBase = _dockingBaseHelper.GetDockingBase(resultBaseEid); if (dockingBase != null && dockingBase.IsDockingAllowed(this) == ErrorCodes.NoError) + { return _dockingBaseHelper.GetDockingBase(resultBaseEid); + } //docking would normally fail if (wasHomeBase) @@ -947,11 +965,11 @@ public DockingBase GetHomeBaseOrCurrentBase() //pick the race related homebase resultBaseEid = DefaultCorporation.GetDockingBaseEid(this); - var thisCharacter = this; + Character thisCharacter = this; //inform dead player about this state Transaction.Current.OnCommited(() => { - var info = new Dictionary + Dictionary info = new Dictionary { {k.characterID,thisCharacter.Id}, {k.baseEID, resultBaseEid}, @@ -964,9 +982,9 @@ public DockingBase GetHomeBaseOrCurrentBase() return _dockingBaseHelper.GetDockingBase(resultBaseEid); } - public void WriteItemTransactionLog(TransactionType transactionType,Item item) + public void WriteItemTransactionLog(TransactionType transactionType, Item item) { - var b = TransactionLogEvent.Builder().SetTransactionType(transactionType).SetCharacter(this).SetItem(item.Definition,item.Quantity); + TransactionLogEventBuilder b = TransactionLogEvent.Builder().SetTransactionType(transactionType).SetCharacter(this).SetItem(item.Definition, item.Quantity); LogTransaction(b); } @@ -974,8 +992,8 @@ public void SetActiveRobot(Robot robot) { ActiveRobotEid = robot?.Eid ?? 0L; - var thisCharacter = this; - var result = robot?.ToDictionary(); + Character thisCharacter = this; + Dictionary result = robot?.ToDictionary(); Transaction.Current.OnCommited(() => Message.Builder.SetCommand(Commands.RobotActivated) .WithData(result) .WrapToResult() @@ -986,28 +1004,28 @@ public void SetActiveRobot(Robot robot) public void CleanGameRelatedData() { //corp founder - Db.Query().CommandText("update corporations set founder=NULL where founder=@characterId") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("update corporations set founder=NULL where founder=@characterId") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); - Db.Query().CommandText("delete characterextensions where characterid=@characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("delete characterextensions where characterid=@characterID") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); - Db.Query().CommandText("delete charactersettings where characterid=@characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("delete charactersettings where characterid=@characterID") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); - Db.Query().CommandText("delete charactersparks where characterid=@characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("delete charactersparks where characterid=@characterID") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); - Db.Query().CommandText("delete charactersparkteleports where characterid=@characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("delete charactersparkteleports where characterid=@characterID") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); - Db.Query().CommandText("delete from channelmembers where memberid = @characterID") - .SetParameter("@characterID",Id) + _ = Db.Query().CommandText("delete from channelmembers where memberid = @characterID") + .SetParameter("@characterID", Id) .ExecuteNonQuery(); TransportAssignment.CharacterDeleted(this); @@ -1028,7 +1046,7 @@ public Gang GetGang() [CanBeNull] public Alliance GetAlliance() { - var allianceEid = AllianceEid; + long allianceEid = AllianceEid; return allianceEid == 0L ? null : Alliance.GetOrThrow(allianceEid); } @@ -1039,14 +1057,14 @@ public bool IsRobotSelectedForCharacter(Robot robot) public DockingBase GetCurrentDockingBase() { - var baseEid = CurrentDockingBaseEid; + long baseEid = CurrentDockingBaseEid; return _dockingBaseHelper.GetDockingBase(baseEid); } [CanBeNull] public Player GetPlayerRobotFromZone() { - var zone = GetCurrentZone(); + IZone zone = GetCurrentZone(); return zone?.GetPlayer(this); } @@ -1055,22 +1073,27 @@ public IZone GetCurrentZone() return _zoneManager.Value.GetZone(ZoneId ?? -1); } + public IZone GetZone(int zoneiwant) + { + return _zoneManager.Value.GetZone(zoneiwant); + } + public ZoneConfiguration GetCurrentZoneConfiguration() { return GetCurrentZone()?.Configuration ?? ZoneConfiguration.None; } - public IDictionary GetFullProfile() + public IDictionary GetFullProfile() { - var record = Db.Query().CommandText("select * from characters where characterid = @characterid") - .SetParameter("@characterid",Id) + System.Data.IDataRecord record = Db.Query().CommandText("select * from characters where characterid = @characterid") + .SetParameter("@characterid", Id) .ExecuteSingleRow().ThrowIfNull(ErrorCodes.CharacterNotFound); - var currentBaseEid = record.GetValue("baseEID"); - var dockingBase = _dockingBaseHelper.GetDockingBase(currentBaseEid); - var isInTraining = record.GetValue("raceID") == 0; + long currentBaseEid = record.GetValue("baseEID"); + DockingBase dockingBase = _dockingBaseHelper.GetDockingBase(currentBaseEid); + bool isInTraining = record.GetValue("raceID") == 0; - var profile = new Dictionary(21) + Dictionary profile = new Dictionary(21) { {k.raceID, record.GetValue("raceID")}, {k.creation, record.GetValue("creation")}, @@ -1098,7 +1121,7 @@ public IDictionary GetFullProfile() {k.isInTraining, isInTraining} }; - _techTreeService.AddInfoToDictionary(Eid,profile); + _techTreeService.AddInfoToDictionary(Eid, profile); return profile; } @@ -1110,7 +1133,7 @@ public PrivateCorporation GetPrivateCorporationOrThrow() [CanBeNull] public PrivateCorporation GetPrivateCorporation() { - return (GetCorporation() as PrivateCorporation); + return GetCorporation() as PrivateCorporation; } public Corporation GetCorporation() @@ -1120,28 +1143,30 @@ public Corporation GetCorporation() public DefaultCorporation GetDefaultCorporation() { - var eid = DefaultCorporationEid; + long eid = DefaultCorporationEid; return (DefaultCorporation)Corporation.GetOrThrow(eid); } public void DeleteAllSpentPoints() { //delete the spent points - Db.Query().CommandText("delete accountextensionspent where characterid=@characterID") + _ = Db.Query().CommandText("delete accountextensionspent where characterid=@characterID") .SetParameter("@characterID", Id) .ExecuteNonQuery(); } public void GetTableIndexForAccountExtensionSpent(int extensionId, int extensionLevel, ref int spentId, ref int spentPoints) { - var record = Db.Query().CommandText("select top 1 id,points from accountextensionspent where extensionid=@extensionID and extensionlevel=@extensionLevel and characterid=@characterID and points > 0 order by eventtime desc") + System.Data.IDataRecord record = Db.Query().CommandText("select top 1 id,points from accountextensionspent where extensionid=@extensionID and extensionlevel=@extensionLevel and characterid=@characterID and points > 0 order by eventtime desc") .SetParameter("@extensionLevel", extensionLevel) .SetParameter("@extensionID", extensionId) .SetParameter("@characterID", Id) .ExecuteSingleRow(); if (record == null) + { return; + } spentId = record.GetValue(0); spentPoints = record.GetValue(1); diff --git a/src/Perpetuum/Accounting/EpForActivityLogEvent.cs b/src/Perpetuum/Accounting/EpForActivityLogEvent.cs index 44685c2af..9feaff923 100644 --- a/src/Perpetuum/Accounting/EpForActivityLogEvent.cs +++ b/src/Perpetuum/Accounting/EpForActivityLogEvent.cs @@ -19,6 +19,8 @@ public EpForActivityLogEvent(EpForActivityType transactionType) public int RawPoints { get; set; } public int Points { get; set; } public double BoostFactor { get; set; } + public int BoostMultiplier { get; set; } + public int EventMultiplier { get; set; } public DateTime Created { get; set; } public IDictionary ToDictionary() @@ -30,6 +32,8 @@ public IDictionary ToDictionary() {k.rawPoints, RawPoints}, {k.points, Points}, {k.boostFactor, BoostFactor}, + {k.multiplier, BoostMultiplier}, + {k.bonusMultiplier, EventMultiplier}, {k.created, Created} }; diff --git a/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs b/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs index 6617cb6f8..b24715f24 100644 --- a/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs +++ b/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs @@ -5,9 +5,9 @@ namespace Perpetuum.Accounting { public class EpForActivityLogger : DbLogger { - protected override void BuildCommand(EpForActivityLogEvent logEvent,DbQuery query) + protected override void BuildCommand(EpForActivityLogEvent logEvent, DbQuery query) { - const string q = @"INSERT dbo.epforactivitylog (accountid, characterid, epforactivitytype, rawpoints, points, boostfactor ) VALUES ( @accountId, @characterId, @epforactivityType, @rawPoints, @points, @boostFactor)"; + const string q = @"INSERT dbo.epforactivitylog (accountid, characterid, epforactivitytype, rawpoints, points, boostfactor, multiplier, bonusMultiplier ) VALUES ( @accountId, @characterId, @epforactivityType, @rawPoints, @points, @boostFactor, @boostMultiplier, @bonusMultiplier)"; query.CommandText(q) .SetParameter("@accountId",logEvent.Account.Id) @@ -15,7 +15,9 @@ protected override void BuildCommand(EpForActivityLogEvent logEvent,DbQuery quer .SetParameter("@epforactivityType", (int)logEvent.TransactionType) .SetParameter("@rawPoints", logEvent.RawPoints) .SetParameter("@points", logEvent.Points) - .SetParameter("@boostFactor", logEvent.BoostFactor); + .SetParameter("@boostFactor", logEvent.BoostFactor) + .SetParameter("@boostMultiplier", logEvent.BoostMultiplier) + .SetParameter("@bonusMultiplier", logEvent.EventMultiplier); } } } \ No newline at end of file diff --git a/src/Perpetuum/Accounting/IAccountManager.cs b/src/Perpetuum/Accounting/IAccountManager.cs index b7c041e5f..5b1f3182d 100644 --- a/src/Perpetuum/Accounting/IAccountManager.cs +++ b/src/Perpetuum/Accounting/IAccountManager.cs @@ -11,26 +11,40 @@ public interface IAccountManager IAccountWallet GetWallet(Account account, AccountTransactionType transactionType); void LogTransaction(AccountTransactionLogEvent e); + IEnumerable GetTransactionHistory(Account account, TimeSpan offset,TimeSpan length); int GetActiveCharactersCount(Account account); int GetLockedEpByAccount(Account account); + int CalculateCurrentEp(Account account); + IDictionary GetEPData(Account account, Character character); + void FreeLockedEp(Account account, int amount); void InsertExtensionRemoveLog(Account account, Character character, int extensionId, int extensionLevel,int points); + void AddExtensionPointsSpent(Account account, Character character, int spentPoints, int extensionID,int extensionLevel); - void ExtensionSubscriptionStart(Account account, DateTime startTime, DateTime endTime); - void ExtensionSubscriptionExtend(Account account, DateTime extendedValidUntil); + + void ExtensionSubscriptionStart(Account account, DateTime startTime, DateTime endTime, int multiplierBonus); void AddExtensionPoints(Account account, int pointsToInject); + int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType,int points); void InsertPenaltyPoint(Account account, AccountExtensionPenaltyType penaltyType, int points, bool forever); + IEnumerable GetEpForActivityHistory(Account account, DateTime earlier, DateTime later); void PackageGenerateAll(Account account); + + /// + /// Unlocks all EP and resets the EP spent. + /// + /// The account to unlock EP. + /// The character to reset EP. + void UnlockEpAndReset(Account account, Character character); } } \ No newline at end of file diff --git a/src/Perpetuum/Argument.cs b/src/Perpetuum/Argument.cs index 6a2d423b6..4382f3ddd 100644 --- a/src/Perpetuum/Argument.cs +++ b/src/Perpetuum/Argument.cs @@ -16,6 +16,8 @@ public void Check(Dictionary data) if (!data.TryGetValue(_name, out object o)) throw new PerpetuumException(ErrorCodes.RequiredArgumentIsNotSpecified); + var foo = o.GetType(); + if (o.GetType() != typeof(T)) throw new PerpetuumException(ErrorCodes.RequiredArgumentIsNotSpecified); } diff --git a/src/Perpetuum/Collections/LazyDictionary.cs b/src/Perpetuum/Collections/LazyDictionary.cs index b0bbc9d82..105309e5c 100644 Binary files a/src/Perpetuum/Collections/LazyDictionary.cs and b/src/Perpetuum/Collections/LazyDictionary.cs differ diff --git a/src/Perpetuum/Collections/WeightedCollection.cs b/src/Perpetuum/Collections/WeightedCollection.cs new file mode 100644 index 000000000..485993190 --- /dev/null +++ b/src/Perpetuum/Collections/WeightedCollection.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Collections +{ + /// + /// Collection that provides random access to stored items where weights bias the probability of selection + /// + /// Any data to be kept in the list + public class WeightedCollection + { + private readonly List _list = new List(); + private int _sumWeights = 0; + public void Add(T item, int weight = 1) + { + _sumWeights += weight; + _list.Add(new WeightedEntry(item, weight)); + } + + public void Clear() + { + _sumWeights = 0; + _list.Clear(); + } + + public T GetRandom() + { + if (_sumWeights == 0) + return default; + + if (_list.Count == 1) + return _list.First().Item; + + var weightTarget = FastRandom.NextInt(_sumWeights - 1); + var current = 0; + var iterator = _list.GetEnumerator(); + while (iterator.MoveNext()) + { + current += iterator.Current.Weight; + if (current > weightTarget) + break; + } + if (iterator.Current != null) + { + return iterator.Current.Item; + } + return default; + } + + private class WeightedEntry + { + public T Item { get; private set; } + public int Weight { get; private set; } + public WeightedEntry(T item, int weight) + { + Item = item; + Weight = weight; + } + } + } +} diff --git a/src/Perpetuum/Commands.cs b/src/Perpetuum/Commands.cs index b3d1809d5..c2cdf63c0 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -82,6 +82,28 @@ public static Command GetCommandByText(string commandText) } }; + //[22:30:34] ERR [UREQ] NoSuchCommand Data = {command=zoneDrawRamp} ip: 127.0.0.1 account: 9 character: 4 Req: zoneDrawRamp:zone_39:#max=n0#size=n60#range=f0.494141#positionx=n1411#positiony=n916#blend=f0.500000 + public static readonly Command ZoneDrawRamp = new Command + { + Text = "zoneDrawRamp", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.max), + new Argument(k.size), + new Argument(k.range), + new Argument("positionx"), + new Argument("positiony"), + new Argument("blend") + } + }; + + public static readonly Command ZoneSmooth = new Command + { + Text = "zoneSmooth", + AccessLevel = AccessLevel.admin, + }; + public static readonly Command ZoneDisplayMissionSpots = new Command { Text = "zoneDisplayMissionSpots", @@ -520,6 +542,19 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command MovePlayer = new Command + { + Text = "movePlayer", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.characterID), + new Argument(k.zoneID), + new Argument(k.x), + new Argument(k.y) + } + }; + public static readonly Command MissionTargetUpdate = new Command { Text = "missionTargetUpdate", @@ -942,6 +977,21 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command UpdateNews = new Command + { + Text = "updateNews", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.title), + new Argument(k.body), + new Argument(k.type), + new Argument(k.language), + new Argument(k.ID), + new Argument(k.time) + } + }; + public static readonly Command CorporationHangarRentExpired = new Command { Text = "corporationHangarRentExpired", @@ -1178,6 +1228,28 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command ZoneCreateTerraformLimit = new Command + { + Text = "ZoneCreateTerraformLimit", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.mode), + new Argument(k.distance) + } + }; + + public static readonly Command ZoneSetLayerWithBitMap = new Command + { + Text = "ZoneSetLayerWithBitMap", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.file), + new Argument(k.flags) + } + }; + public static readonly Command ZoneSampleEnvironment = new Command { Text = "zoneSampleEnvironment", @@ -1236,6 +1308,28 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command ZoneCopyGroundType = new Command + { + Text = "zoneCopyGroundType", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.source), + new Argument(k.target) + } + }; + + public static readonly Command ZoneFillGroundTypeRandom = new Command + { + Text = "zoneFillGroundTypeRandom", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.size), + new Argument(k.numberOfRuns) + } + }; + public static readonly Command ZoneSetBaseDetails = new Command { Text = "zoneSetBaseDetails", @@ -2632,6 +2726,16 @@ public static Command GetCommandByText(string commandText) AccessLevel = AccessLevel.normal }; + public static readonly Command ForceDockAdmin = new Command + { + Text = "forceDockAdmin", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.characterID) + } + }; + public static readonly Command ZoneSaveLayer = new Command { Text = "zoneSaveLayer", @@ -2779,6 +2883,12 @@ public static Command GetCommandByText(string commandText) AccessLevel = AccessLevel.notDefined }; + public static readonly Command GetCommands = new Command + { + Text = "getCommands", + AccessLevel = AccessLevel.notDefined + }; + public static readonly Command GetZoneInfo = new Command { Text = "getZoneInfo", @@ -3211,6 +3321,12 @@ public static Command GetCommandByText(string commandText) AccessLevel = AccessLevel.normal }; + public static readonly Command ChannelListAll = new Command + { + Text = "channelListAll", + AccessLevel = AccessLevel.admin + }; + public static readonly Command ChannelMyList = new Command { Text = "channelMyList", @@ -3412,6 +3528,29 @@ public static Command GetCommandByText(string commandText) } }; // + //GameAdmin Command + public static readonly Command ExtensionFreeAllLockedEpCommand = new Command + { + Text = "extensionFreeAllLockedEpByCommand", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.accountID) + } + }; + + //GameAdmin Command + public static readonly Command EPBonusSet = new Command + { + Text = "EPBonusSet", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.bonus), + new Argument(k.duration), + } + }; + public static readonly Command FreshNewsCount = new Command { Text = "freshNewsCount", @@ -4153,7 +4292,7 @@ public static Command GetCommandByText(string commandText) public static readonly Command AccountList = new Command { Text = "accountList", - AccessLevel = AccessLevel.toolAdmin + AccessLevel = AccessLevel.admin }; public static readonly Command AccountGetTransactionHistory = new Command @@ -4486,7 +4625,7 @@ public static Command GetCommandByText(string commandText) public static readonly Command ZoneSelfDestruct = new Command { Text = "zoneSelfDestruct", - AccessLevel = AccessLevel.normal + AccessLevel = AccessLevel.admin }; public static readonly Command ZoneGetBuildings = new Command @@ -5257,13 +5396,19 @@ public static Command GetCommandByText(string commandText) public static readonly Command GetAccountsWithCharacters = new Command { Text = "getAccountsWithCharacters", - AccessLevel = AccessLevel.toolAdmin, + AccessLevel = AccessLevel.admin, + }; + + public static readonly Command GetCharactersOnline = new Command + { + Text = "getCharactersOnline", + AccessLevel = AccessLevel.admin, }; public static readonly Command AccountGet = new Command { Text = "accountGet", - AccessLevel = AccessLevel.toolAdmin, + AccessLevel = AccessLevel.admin, Arguments = { new Argument(k.accountID) @@ -5284,6 +5429,12 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command ReimburseItem = new Command + { + Text = "ReimburseItem", + AccessLevel = AccessLevel.admin + }; + // creates an account from the tool. // // open version: use AccountOpenCreate @@ -5337,13 +5488,23 @@ public static Command GetCommandByText(string commandText) } }; + // confirm email for account. From GM interface. + public static readonly Command AccountConfirmEmail = new Command + { + Text = "accountConfirmEmail", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.accountID) + } + }; // ban account and disconnect if online // public static readonly Command AccountBan = new Command { Text = "accountBan", - AccessLevel = AccessLevel.toolAdmin, + AccessLevel = AccessLevel.admin, Arguments = { new Argument(k.accountID), @@ -5354,7 +5515,7 @@ public static Command GetCommandByText(string commandText) public static readonly Command AccountUnban = new Command { Text = "accountUnban", - AccessLevel = AccessLevel.toolAdmin, + AccessLevel = AccessLevel.admin, Arguments = { new Argument(k.accountID), diff --git a/src/Perpetuum/Comparers/EntityComparer.cs b/src/Perpetuum/Comparers/EntityComparer.cs new file mode 100644 index 000000000..560e40aba --- /dev/null +++ b/src/Perpetuum/Comparers/EntityComparer.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Perpetuum.EntityFramework; + +namespace Perpetuum.Comparers +{ + public class EntityComparer : IEqualityComparer + { + public EntityComparer() + { + } + + public bool Equals(IEntity left, IEntity right) + { + // If they're both null, technically they're the same... + if (ReferenceEquals(left, right)) return true; + + if (left is null || right is null) return false; + + return left.Eid == right.Eid; + } + + public int GetHashCode(IEntity obj) + { + if (obj is null) return 0; + + return obj.Eid.GetHashCode(); + } + } +} diff --git a/src/Perpetuum/Containers/Container.Helpers.cs b/src/Perpetuum/Containers/Container.Helpers.cs index 5072352bc..b1f971196 100644 --- a/src/Perpetuum/Containers/Container.Helpers.cs +++ b/src/Perpetuum/Containers/Container.Helpers.cs @@ -1,6 +1,7 @@ using Perpetuum.Accounting.Characters; using Perpetuum.EntityFramework; using Perpetuum.Items; +using Perpetuum.Items.Helpers; using Perpetuum.Units.DockingBases; using Perpetuum.Units.FieldTerminals; diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs index 131164f21..c16333d90 100644 Binary files a/src/Perpetuum/Converters/DelegateConverter.cs and b/src/Perpetuum/Converters/DelegateConverter.cs differ diff --git a/src/Perpetuum/Converters/IConverter.cs b/src/Perpetuum/Converters/IConverter.cs index c5cddff54..444e3e0f7 100644 Binary files a/src/Perpetuum/Converters/IConverter.cs and b/src/Perpetuum/Converters/IConverter.cs differ diff --git a/src/Perpetuum/Converters/IConvertible.cs b/src/Perpetuum/Converters/IConvertible.cs index 8d88cec3a..175c60b6e 100644 Binary files a/src/Perpetuum/Converters/IConvertible.cs and b/src/Perpetuum/Converters/IConvertible.cs differ diff --git a/src/Perpetuum/CustomDictionary.cs b/src/Perpetuum/CustomDictionary.cs new file mode 100644 index 000000000..b3cbf27f3 --- /dev/null +++ b/src/Perpetuum/CustomDictionary.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json; +using Perpetuum.Log; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum +{ + public interface ICustomDictionary + { + Dictionary GetDictionary(int language); + } + + public class CustomDictionary : ICustomDictionary + { + /* + Language IDs: + 0 = English + 1 = Hungarian + 2 = German + 3 = Portuguese + 4 = Russian + 5 = French + 6 = Spanish + 7 = Polish + 8 = Slovenian + 9 = Romanian + 10 = Norwegian + 11 = Greek + 12 = Finnish + 13 = Italian + 14 = Turkish + 15 = Estonian + 16 = Swedish + 17 = Dutch + */ + + private readonly int _defaultLanguage = 0; + public Dictionary> _dictionaries; + + public CustomDictionary(IO.IFileSystem fileManager) + { + _dictionaries = new Dictionary>(); + + // if the directory doesn't exist or the JSON is malformed just throw an exception, log it and move on. + // Otherwise we get a blank screen on the client. + try + { + var files = fileManager.GetFiles("customDictionary", "*.json"); + + foreach (var file in files) + { + var languageID = Convert.ToInt32(System.IO.Path.GetFileNameWithoutExtension(file)); + var settingsFile = fileManager.ReadAllText(file); + var dictionary = JsonConvert.DeserializeObject>(settingsFile); + _dictionaries.Add(languageID, dictionary); + } + } + catch(Exception ex) + { + Logger.Error(ex.Message); + } + } + + public Dictionary GetDictionary(int language) + { + if (_dictionaries.ContainsKey(language)) + { + return _dictionaries[language]; + } + else if (_dictionaries.ContainsKey(_defaultLanguage)) + { + return _dictionaries[_defaultLanguage]; + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Data/Db.cs b/src/Perpetuum/Data/Db.cs index b11b475f3..294eb0b85 100644 --- a/src/Perpetuum/Data/Db.cs +++ b/src/Perpetuum/Data/Db.cs @@ -6,7 +6,6 @@ namespace Perpetuum.Data { public sealed class Db { - public static Func TransactionScopeFactory { get; set; } public static Func DbQueryFactory { get; set; } public static DbQuery Query() @@ -33,7 +32,10 @@ public static Task CreateTransactionAsync(Action action) public static TransactionScope CreateTransaction() { - return TransactionScopeFactory(); + return new TransactionScope(TransactionScopeOption.Required, new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted + }); } } } \ No newline at end of file diff --git a/src/Perpetuum/Deployers/AreaBombDeployer.cs b/src/Perpetuum/Deployers/AreaBombDeployer.cs index 12db34903..f12c5b37b 100644 --- a/src/Perpetuum/Deployers/AreaBombDeployer.cs +++ b/src/Perpetuum/Deployers/AreaBombDeployer.cs @@ -4,6 +4,7 @@ using Perpetuum.Units; using Perpetuum.Units.DockingBases; using Perpetuum.Zones; +using Perpetuum.Zones.Eggs; using Perpetuum.Zones.Teleporting; namespace Perpetuum.Deployers @@ -30,6 +31,11 @@ protected override ErrorCodes CanDeploy(IZone zone, Unit unit, Position spawnPos if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.AREA_BOMB_DISTANCE_TO_TELEPORTS).Any()) return ErrorCodes.NotDeployableNearObject; } + else + { + if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.GAMMA_BOMB_STACK_DISTANCE).Any()) + return ErrorCodes.TooCloseToOtherDevice; + } return base.CanDeploy(zone, unit, spawnPosition, player); } diff --git a/src/Perpetuum/EntityFramework/DefinitionConfig.cs b/src/Perpetuum/EntityFramework/DefinitionConfig.cs index 125f6457e..bfce9500f 100644 --- a/src/Perpetuum/EntityFramework/DefinitionConfig.cs +++ b/src/Perpetuum/EntityFramework/DefinitionConfig.cs @@ -21,6 +21,7 @@ public class DefinitionConfig public double? damage_explosive; public double? damage_kinetic; public double? damage_thermal; + public double? damage_toxic; public int? lifeTime; public int? activationTime; public int? waves; @@ -111,6 +112,7 @@ public DefinitionConfig(IDataRecord record) damage_explosive = record.GetValue("damage_explosive"); damage_kinetic = record.GetValue("damage_kinetic"); damage_thermal = record.GetValue("damage_thermal"); + damage_toxic = record.GetValue("damage_toxic"); lifeTime = record.GetValue("lifetime"); activationTime = record.GetValue("activationtime"); waves = record.GetValue("waves"); @@ -140,7 +142,6 @@ public DefinitionConfig(IDataRecord record) network_node_range = record.GetValue("network_node_range"); _hitSize = record.GetValue("hitsize") ?? 1.41; - var tint = record.GetValue("tint"); if (!string.IsNullOrEmpty(tint)) @@ -161,6 +162,7 @@ public IDictionary ToDictionary() {k.damage_explosive, damage_explosive}, {k.damage_kinetic, damage_kinetic}, {k.damage_thermal, damage_thermal}, + {k.damage_toxic, damage_toxic}, {k.lifeTime, lifeTime}, {k.activationTime, activationTime}, {k.waves, waves}, diff --git a/src/Perpetuum/EntityFramework/Entity.cs b/src/Perpetuum/EntityFramework/Entity.cs index d2cabadb5..92cea2a73 100644 --- a/src/Perpetuum/EntityFramework/Entity.cs +++ b/src/Perpetuum/EntityFramework/Entity.cs @@ -9,7 +9,7 @@ namespace Perpetuum.EntityFramework { - public class Entity + public class Entity : IEntity { public static IEntityServices Services { get; set; } @@ -84,7 +84,7 @@ public Entity ParentEntity [CanBeNull] public Entity GetOrLoadParentEntity() { - if (_parentEntity == null && Parent > 0) + if ((_parentEntity == null || _parentEntity.Eid <= 0) && Parent > 0) { _parentEntity = LoadParentEntity(Parent); } diff --git a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs index 24f1bf455..9e661ad76 100644 --- a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs +++ b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs @@ -1,8 +1,10 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Perpetuum.ExportedTypes; using Perpetuum.GenXY; +using Perpetuum.Zones.RemoteControl; namespace Perpetuum.EntityFramework { @@ -17,6 +19,11 @@ public EntityDefaultOptions(IDictionary dictionary) _dictionary = dictionary; } + public TReturnType GetOption(string keyName) + { + return _dictionary.GetOrDefault(keyName); + } + public int AlarmPeriod { get { return _dictionary.GetOrDefault(k.alarmPeriod); } @@ -169,5 +176,26 @@ public int SparkID return id; } } + + public int TurretId + { + get + { + var id = _dictionary.GetOrDefault(k.TurretId, 0); + Debug.Assert(id > 0); + + return id; + } + } + + public TurretType TurretType + { + get + { + var typeString = _dictionary.GetOrDefault("turretType"); + + return (TurretType)Enum.Parse(typeof(TurretType), typeString); + } + } } } \ No newline at end of file diff --git a/src/Perpetuum/EntityFramework/EntityRepositoryExtensions.cs b/src/Perpetuum/EntityFramework/EntityRepositoryExtensions.cs index db6972c5e..2e34eeb37 100644 --- a/src/Perpetuum/EntityFramework/EntityRepositoryExtensions.cs +++ b/src/Perpetuum/EntityFramework/EntityRepositoryExtensions.cs @@ -14,6 +14,14 @@ public static void DeleteTree(this IEntityRepository repository,long eid) repository.Delete(entity); } + public static void DeleteTreeButNotRoot(this IEntityRepository repository, long eid) + { + var entity = repository.LoadTree(eid, null).ThrowIfNull(ErrorCodes.EntityNotFound); + foreach(var child in entity.Children){ + repository.Delete(child); + } + } + public static void ForceUpdate(this IEntityRepository repository, Entity entity) { foreach (var child in entity.Children) diff --git a/src/Perpetuum/EntityFramework/IEntity.cs b/src/Perpetuum/EntityFramework/IEntity.cs new file mode 100644 index 000000000..66b8c7cb1 --- /dev/null +++ b/src/Perpetuum/EntityFramework/IEntity.cs @@ -0,0 +1,8 @@ +using System; +namespace Perpetuum.EntityFramework +{ + public interface IEntity + { + long Eid { get; } + } +} diff --git a/src/Perpetuum/EnumHelper.cs b/src/Perpetuum/EnumHelper.cs index 55596604f..b1667db86 100644 --- a/src/Perpetuum/EnumHelper.cs +++ b/src/Perpetuum/EnumHelper.cs @@ -6,6 +6,39 @@ namespace Perpetuum { public static class EnumHelper { + /// + /// Get the enum entry from its name + /// + /// enum type + /// name of enum entry + /// Enum entry + public static T GetEnumFromName(string name) + { + return (T)Enum.Parse(typeof(T), name, true); + } + + /// + /// Get the string Name of the enum entry by value + /// + /// Enum type + /// value of the enum entry + /// string name of the entry + public static string GetEnumName(object value) + { + return Enum.GetName(typeof(T), value); + } + + /// + /// Get the enum entry from its value + /// + /// Enum type + /// value of enum entry + /// Enum + public static T GetEnum(object value) + { + return GetEnumFromName(GetEnumName(value)); + } + /// /// Converts an enum to a dictionary /// diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index d28566fae..39ffed3bf 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -701,6 +701,18 @@ public enum ErrorCodes MissionNoItemsFoundToDeliver, InviteOnlyServer, SteamLoginDisabled, - AccountAlreadyExists + AccountAlreadyExists, + RobotWrongType, // 666 + PBSTechLevelTooHighForZone, + TooCloseToOtherDevice, + TooCloseToNPCBase, + OnlyBuildableOnTerraformProtected, + TechLevelTooLow, + MaxExpiringBasePerZoneReached, + MaxExpiringBasePerZonePerCorpReached, + RespecTokenIsNotReadyYet, + MaxBandwidthExceed, + RemoteControlledTurretInRange, + TurretNotDeployableNearObject, } } diff --git a/src/Perpetuum/GenericTypeExtensions.cs b/src/Perpetuum/GenericTypeExtensions.cs index d58694325..44a923f6f 100644 --- a/src/Perpetuum/GenericTypeExtensions.cs +++ b/src/Perpetuum/GenericTypeExtensions.cs @@ -66,6 +66,5 @@ public static T Clone(this T source) return (T)(bf.Deserialize(ms)); } } - } } diff --git a/src/Perpetuum/GlobalConfiguration.cs b/src/Perpetuum/GlobalConfiguration.cs index b79bb32fa..7cfbe2de4 100644 --- a/src/Perpetuum/GlobalConfiguration.cs +++ b/src/Perpetuum/GlobalConfiguration.cs @@ -1,3 +1,6 @@ +using Newtonsoft.Json; +using System.ComponentModel; + namespace Perpetuum { public class GlobalConfiguration @@ -16,6 +19,24 @@ public class GlobalConfiguration public int SteamAppID { get; set; } public byte[] SteamKey { get; set; } + public string ResourceServerURL { get; set; } + + public bool EnableDev { get; set; } + public CorporationConfiguration Corporation { get; set; } + + public bool StartServerInAdminOnlyMode { get; set; } + + // Default NIC value for new player. + [DefaultValue(500000), JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int StartCredit { get; set; } + + // Default NIC per level value for new player. + [DefaultValue(125000), JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int LevelCredit { get; set; } + + // Default EP value for new player. + [DefaultValue(40000), JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int StartEP { get; set; } } -} \ No newline at end of file +} diff --git a/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs b/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs index c53966fa1..a42a99c78 100644 --- a/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs +++ b/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs @@ -50,6 +50,7 @@ public int GetMaximumProbeAmount() return CEO.GetExtensionLevel(maxProbesExtensionId); } + // TODO: Find a way to use joins instead of this public IEnumerable GetProximityProbeEids() { var probeDefinitions = EntityServices.Defaults.GetAll().GetDefinitionsByCategoryFlag(CategoryFlags.cf_proximity_probes); @@ -60,5 +61,23 @@ public IEnumerable GetProximityProbeEids() .Select(r => r.GetValue(0)) .ToArray(); } + + // TODO: Find a way to use joins instead of this + public IEnumerable GetLandMineEids() + { + var lightlandMineDefinitions = EntityServices.Defaults.GetAll().GetDefinitionsByCategoryFlag(CategoryFlags.cf_light_landmines); + var mediumlandMineDefinitions = EntityServices.Defaults.GetAll().GetDefinitionsByCategoryFlag(CategoryFlags.cf_medium_landmines); + var heavylandMineDefinitions = EntityServices.Defaults.GetAll().GetDefinitionsByCategoryFlag(CategoryFlags.cf_heavy_landmines); + var landMineDefinitions = lightlandMineDefinitions.Concat(mediumlandMineDefinitions).Concat(heavylandMineDefinitions); + + var queryStr = $"SELECT eid FROM entities WHERE owner=@corporationEID and definition in ({landMineDefinitions.ArrayToString()})"; + + return Db.Query() + .CommandText(queryStr) + .SetParameter("@corporationEID", Eid) + .Execute() + .Select(r => r.GetValue(0)) + .ToArray(); + } } } diff --git a/src/Perpetuum/Host/HostShutDownManager.cs b/src/Perpetuum/Host/HostShutDownManager.cs index 75f0f410e..5c7138f18 100644 --- a/src/Perpetuum/Host/HostShutDownManager.cs +++ b/src/Perpetuum/Host/HostShutDownManager.cs @@ -35,6 +35,9 @@ public void StopShutDown(Command command) _isInShutDown = false; _shutDownTime = default(DateTime); + // create the cancellation token, logs, etc. + CancelShutdown(); + //send all clients the cancellation SendToAll(command); } diff --git a/src/Perpetuum/IO/FileSystem.cs b/src/Perpetuum/IO/FileSystem.cs index df4f1cdd1..f0cb9766c 100644 --- a/src/Perpetuum/IO/FileSystem.cs +++ b/src/Perpetuum/IO/FileSystem.cs @@ -73,6 +73,11 @@ public string CreatePath(string path) return Path.Combine(_root, path); } + public IEnumerable GetFiles(string path, string mask) + { + return Directory.GetFiles(Path.Combine(_root, path), mask); + } + public override string ToString() { return $"Root: {_root}"; diff --git a/src/Perpetuum/IO/IFileSystem.cs b/src/Perpetuum/IO/IFileSystem.cs index c91c21fb3..2f4bff3db 100644 --- a/src/Perpetuum/IO/IFileSystem.cs +++ b/src/Perpetuum/IO/IFileSystem.cs @@ -20,5 +20,7 @@ public interface IFileSystem void CreateDirectory(string path); string CreatePath(string path); + + IEnumerable GetFiles(string path, string mask); } } \ No newline at end of file diff --git a/src/Perpetuum/Items/Ammos/Ammo.cs b/src/Perpetuum/Items/Ammos/Ammo.cs index d3089468c..796103ec4 100644 --- a/src/Perpetuum/Items/Ammos/Ammo.cs +++ b/src/Perpetuum/Items/Ammos/Ammo.cs @@ -7,18 +7,11 @@ namespace Perpetuum.Items.Ammos { public class Ammo : Item { - public double BulletTime - { - get { return ED.Options.BulletTime; } - } + public double BulletTime => ED.Options.BulletTime; - public ItemPropertyModifier OptimalRangePropertyModifier - { - get - { - return GetPropertyModifier(AggregateField.optimal_range); - } - } + public ItemPropertyModifier OptimalRangePropertyModifier => GetPropertyModifier(AggregateField.optimal_range); + + public ItemPropertyModifier FalloffRangePropertyModifier => GetPropertyModifier(AggregateField.falloff); public override void AcceptVisitor(IEntityVisitor visitor) { diff --git a/src/Perpetuum/Items/Item.cs b/src/Perpetuum/Items/Base/Item.cs similarity index 60% rename from src/Perpetuum/Items/Item.cs rename to src/Perpetuum/Items/Base/Item.cs index 069ed6829..59991e30e 100644 --- a/src/Perpetuum/Items/Item.cs +++ b/src/Perpetuum/Items/Base/Item.cs @@ -4,102 +4,33 @@ using System.Linq; using System.Threading; using Perpetuum.Accounting.Characters; -using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; namespace Perpetuum.Items { - public class DefaultPropertyModifierReader - { - private ILookup _modifiers; - - public void Init() - { - _modifiers = Db.Query().CommandText("select * from aggregatevalues").Execute().ToLookup(r => r.GetValue("definition"),r => - { - var field = r.GetValue("field"); - var value = r.GetValue("value"); - return ItemPropertyModifier.Create(field, value); - }); - } - - public ItemPropertyModifier[] GetByDefinition(int definition) - { - return _modifiers.GetOrEmpty(definition); - } - } - - public interface IPropertyModifierCollection - { - bool TryGetPropertyModifier(AggregateField field, out ItemPropertyModifier modifier); - ItemPropertyModifier GetPropertyModifier(AggregateField field); - IEnumerable All { get; } - } - - public static class PropertyModifierCollectionExtensions - { - public static IPropertyModifierCollection Combine(this IPropertyModifierCollection source,IPropertyModifierCollection target) - { - var x = source.All.Select(s => ItemPropertyModifier.Modify(s, target.GetPropertyModifier(s.Field))); - return new PropertyModifierCollection(x); - } - } - public delegate IPropertyModifierCollection PropertyModifierCollectionFactory(int definition); - public class PropertyModifierCollection : IPropertyModifierCollection - { - public static readonly PropertyModifierCollection Empty = new PropertyModifierCollection(); - private readonly Dictionary _modifiers = new Dictionary(); - - private PropertyModifierCollection() - { - - } - - public PropertyModifierCollection(IEnumerable modifiers) - { - _modifiers = modifiers.ToDictionary(m => m.Field); - } - - public bool TryGetPropertyModifier(AggregateField field, out ItemPropertyModifier modifier) - { - return _modifiers.TryGetValue(field, out modifier); - } - - public ItemPropertyModifier GetPropertyModifier(AggregateField field) - { - if (TryGetPropertyModifier(field,out ItemPropertyModifier m)) - return m; - - return ItemPropertyModifier.Create(field); - } - - public IEnumerable All => _modifiers.Values; - } - - public delegate void ItemEventHandler(Item unit, T args); public class Item : Entity { - private readonly List _properties = new List(); - private ImmutableHashSet _changedProperties = ImmutableHashSet.Empty; + private readonly List properties = new List(); + private ImmutableHashSet changedProperties = ImmutableHashSet.Empty; public IPropertyModifierCollection BasePropertyModifiers { get; set; } - public IEnumerable Properties => _properties; + public IEnumerable Properties => properties; public void AddProperty(ItemProperty property) { - _properties.Add(property); + properties.Add(property); property.PropertyChanged += OnPropertyChanged; } public virtual void UpdateRelatedProperties(AggregateField field) { - foreach (var property in _properties) + foreach (var property in properties) { property.UpdateIfRelated(field); } @@ -107,7 +38,7 @@ public virtual void UpdateRelatedProperties(AggregateField field) public virtual void UpdateAllProperties() { - foreach (var property in _properties) + foreach (var property in properties) { property.Update(); } @@ -117,13 +48,15 @@ public virtual void UpdateAllProperties() protected virtual void OnPropertyChanged(ItemProperty property) { - ImmutableInterlocked.Update(ref _changedProperties,c => c.Add(property)); + ImmutableInterlocked.Update(ref changedProperties,c => c.Add(property)); PropertyChanged?.Invoke(this, property); } protected ImmutableHashSet GetChangedProperties() { - return Interlocked.CompareExchange(ref _changedProperties, ImmutableHashSet.Empty,_changedProperties); + return Interlocked.CompareExchange( + ref changedProperties, + ImmutableHashSet.Empty,changedProperties); } public void AddPropertiesToDictionary(IDictionary dictionary) @@ -149,12 +82,14 @@ public virtual Dictionary BuildPropertiesDictionary() { property.AddToDictionary(d); } + return d; } public ItemPropertyModifier GetBasePropertyModifier(AggregateField field) { var modifier = BasePropertyModifiers.GetPropertyModifier(field); + return modifier; } @@ -178,10 +113,14 @@ public virtual bool IsStackable get { if (ED.AttributeFlags.AlwaysStackable) + { return true; + } if (ED.AttributeFlags.NonStackable) + { return false; + } return !IsDamaged; } @@ -215,13 +154,17 @@ public virtual void Initialize() public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } public void CheckOwnerCharacterAndCorporationAndThrowIfFailed(Character character) { if (Owner == 0) + { return; + } if (Owner != character.CorporationEid) { @@ -232,7 +175,9 @@ public void CheckOwnerCharacterAndCorporationAndThrowIfFailed(Character characte public void CheckOwnerOnlyCharacterAndThrowIfFailed(Character character) { if (Owner == 0) + { return; + } Owner.ThrowIfNotEqual(character.Eid, ErrorCodes.OwnerMismatch); } @@ -241,35 +186,46 @@ public override Dictionary ToDictionary() { var dictionary = base.ToDictionary(); AddPropertiesToDictionary(dictionary); + return dictionary; } public ErrorCodes CanStackTo(Item target) { if (target.Eid == Eid) + { return ErrorCodes.WTFErrorMedicalAttentionSuggested; + } //they must be the same type if (target.ED != ED) + { return ErrorCodes.ItemTypeMismatch; + } //non stackable flag check if (target.ED.AttributeFlags.NonStackable) + { return ErrorCodes.ItemNotStackable; + } if (Math.Abs(target.Health - Health) > double.Epsilon) + { return ErrorCodes.ItemHealthMismatch; + } //if stackable is it repackaged? - if (!target.ED.AttributeFlags.AlwaysStackable) + if (!target.ED.AttributeFlags.AlwaysStackable && + (!target.IsRepackaged || !IsRepackaged)) { - if (!target.IsRepackaged || !IsRepackaged) - return ErrorCodes.ItemHasToBeRepackaged; + return ErrorCodes.ItemHasToBeRepackaged; } var sumQty = (decimal)target.Quantity + Quantity; if (sumQty > int.MaxValue) + { return ErrorCodes.MaximumStackSizeExceeded; + } return ErrorCodes.NoError; } @@ -300,7 +256,9 @@ public IEnumerable UnstackAll() public Item Unstack(int amount) { if (Quantity <= amount) + { return this; + } amount.ThrowIfLess(1, ErrorCodes.AccessDenied); @@ -322,7 +280,9 @@ public Item Unstack(int amount) [NotNull] public static Item GetOrThrow(long itemEid) { - return (Item)Repository.LoadTree(itemEid, null).ThrowIfNull(ErrorCodes.ItemNotFound); + return (Item)Repository + .LoadTree(itemEid, null) + .ThrowIfNull(ErrorCodes.ItemNotFound); } public static Item CreateWithRandomEid(ItemInfo itemInfo) @@ -331,42 +291,8 @@ public static Item CreateWithRandomEid(ItemInfo itemInfo) item.Quantity = itemInfo.Quantity; item.Health = itemInfo.Health; item.IsRepackaged = itemInfo.IsRepackaged; - return item; - } - } - - public class ItemHelper - { - private readonly IEntityServices _entityServices; - public ItemHelper(IEntityServices entityServices) - { - _entityServices = entityServices; - } - - [NotNull] - public Item LoadItemOrThrow(long itemEid) - { - var item = LoadItem(itemEid); - if (item == null) - throw new PerpetuumException(ErrorCodes.ItemNotFound); return item; } - - [CanBeNull] - public Item LoadItem(long itemEid) - { - return (Item) _entityServices.Repository.LoadTree(itemEid, null); - } - - public Item CreateItem(EntityDefault entityDefault, EntityIDGenerator idGenerator) - { - return (Item) _entityServices.Factory.Create(entityDefault, idGenerator); - } - - public Item CreateItem(int definition, EntityIDGenerator idGenerator) - { - return (Item) _entityServices.Factory.Create(definition, idGenerator); - } } } diff --git a/src/Perpetuum/Items/CalibrationProgramCapsule.cs b/src/Perpetuum/Items/CalibrationProgramCapsule.cs new file mode 100644 index 000000000..10c13ae09 --- /dev/null +++ b/src/Perpetuum/Items/CalibrationProgramCapsule.cs @@ -0,0 +1,46 @@ +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using System.Collections.Generic; +using System.Data; + +namespace Perpetuum.Items +{ + + public class CalibrationProgramCapsule : Item + { + public static IDictionary CalibrationTemplateItemLookup; + + static CalibrationProgramCapsule() + { + CalibrationTemplateItemLookup = Database.CreateCache("calibrationtemplateitems", "definition", r => new CalibrationCapsuleRecord(r)); + } + + + IEntityDefaultReader _entityDefaultReader; + + + public CalibrationProgramCapsule(IEntityDefaultReader entityDefaultReader) : base() + { + _entityDefaultReader = entityDefaultReader; + } + + + public EntityDefault Activate() + { + CalibrationCapsuleRecord record = CalibrationTemplateItemLookup.GetOrDefault(Definition); + return _entityDefaultReader.Get(record.TargetDefinition); + } + + } + + + public class CalibrationCapsuleRecord + { + public readonly int TargetDefinition; + + public CalibrationCapsuleRecord(IDataRecord record) + { + TargetDefinition = record.GetValue("targetdefinition"); + } + } +} diff --git a/src/Perpetuum/Items/DefaultPropertyModifierReader.cs b/src/Perpetuum/Items/DefaultPropertyModifierReader.cs new file mode 100644 index 000000000..f89be809e --- /dev/null +++ b/src/Perpetuum/Items/DefaultPropertyModifierReader.cs @@ -0,0 +1,30 @@ +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +using System.Linq; + +namespace Perpetuum.Items +{ + public class DefaultPropertyModifierReader + { + private ILookup modifiers; + + public void Init() + { + modifiers = Db.Query() + .CommandText("select * from aggregatevalues") + .Execute() + .ToLookup(r => r.GetValue("definition"), r => + { + var field = r.GetValue("field"); + var value = r.GetValue("value"); + + return ItemPropertyModifier.Create(field, value); + }); + } + + public ItemPropertyModifier[] GetByDefinition(int definition) + { + return modifiers.GetOrEmpty(definition); + } + } +} diff --git a/src/Perpetuum/Items/EPBoost.cs b/src/Perpetuum/Items/EPBoost.cs new file mode 100644 index 000000000..d95dec13a --- /dev/null +++ b/src/Perpetuum/Items/EPBoost.cs @@ -0,0 +1,41 @@ +using Perpetuum.Accounting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Items +{ + public class EPBoost : Item + { + public int Boost + { + get + { + return ED.Options.GetOption("addBoost"); + } + } + + public int TimePeriodHours + { + get + { + return ED.Options.GetOption("timePeriodHours"); + } + } + + public string Tier + { + get + { + return ED.Options.GetOption("tier"); + } + } + + public void Activate(IAccountManager accountManager, Account account) + { + accountManager.ExtensionSubscriptionStart(account, DateTime.Now, DateTime.Now.AddHours(TimePeriodHours), Boost); + } + } +} diff --git a/src/Perpetuum/Items/Extensions/PropertyModifierCollectionExtensions.cs b/src/Perpetuum/Items/Extensions/PropertyModifierCollectionExtensions.cs new file mode 100644 index 000000000..962a84929 --- /dev/null +++ b/src/Perpetuum/Items/Extensions/PropertyModifierCollectionExtensions.cs @@ -0,0 +1,14 @@ +using System.Linq; + +namespace Perpetuum.Items.Extensions +{ + public static class PropertyModifierCollectionExtensions + { + public static IPropertyModifierCollection Combine(this IPropertyModifierCollection source, IPropertyModifierCollection target) + { + var x = source.All.Select(s => ItemPropertyModifier.Modify(s, target.GetPropertyModifier(s.Field))); + + return new PropertyModifierCollection(x); + } + } +} diff --git a/src/Perpetuum/Items/Helpers/ItemHelper.cs b/src/Perpetuum/Items/Helpers/ItemHelper.cs new file mode 100644 index 000000000..cc5cd4f07 --- /dev/null +++ b/src/Perpetuum/Items/Helpers/ItemHelper.cs @@ -0,0 +1,39 @@ +using Perpetuum.EntityFramework; + +namespace Perpetuum.Items.Helpers +{ + public class ItemHelper + { + private readonly IEntityServices _entityServices; + + public ItemHelper(IEntityServices entityServices) + { + _entityServices = entityServices; + } + + [NotNull] + public Item LoadItemOrThrow(long itemEid) + { + var item = LoadItem(itemEid); + if (item == null) + throw new PerpetuumException(ErrorCodes.ItemNotFound); + return item; + } + + [CanBeNull] + public Item LoadItem(long itemEid) + { + return (Item)_entityServices.Repository.LoadTree(itemEid, null); + } + + public Item CreateItem(EntityDefault entityDefault, EntityIDGenerator idGenerator) + { + return (Item)_entityServices.Factory.Create(entityDefault, idGenerator); + } + + public Item CreateItem(int definition, EntityIDGenerator idGenerator) + { + return (Item)_entityServices.Factory.Create(definition, idGenerator); + } + } +} diff --git a/src/Perpetuum/Items/IPropertyModifierCollection.cs b/src/Perpetuum/Items/IPropertyModifierCollection.cs new file mode 100644 index 000000000..52913f639 --- /dev/null +++ b/src/Perpetuum/Items/IPropertyModifierCollection.cs @@ -0,0 +1,14 @@ +using Perpetuum.ExportedTypes; +using System.Collections.Generic; + +namespace Perpetuum.Items +{ + public interface IPropertyModifierCollection + { + bool TryGetPropertyModifier(AggregateField field, out ItemPropertyModifier modifier); + + ItemPropertyModifier GetPropertyModifier(AggregateField field); + + IEnumerable All { get; } + } +} diff --git a/src/Perpetuum/Items/ItemInfo.cs b/src/Perpetuum/Items/ItemInfo.cs index f7df6a148..d1ffa1dcb 100644 --- a/src/Perpetuum/Items/ItemInfo.cs +++ b/src/Perpetuum/Items/ItemInfo.cs @@ -6,10 +6,14 @@ namespace Perpetuum.Items { public struct ItemInfo : IEquatable { - public static readonly ItemInfo None = new ItemInfo(0); + public static readonly ItemInfo None = new ItemInfo(0,0); public int Definition { get; private set; } public int Quantity { get; set; } + // min/max quantity for loot generation + public int MinQty { get; set; } + public int MaxQty { get; set; } + // public float Health { get; set; } public bool IsRepackaged { get; set; } @@ -21,6 +25,27 @@ public ItemInfo(int definition,int quantity = 1) : this() IsRepackaged = EntityDefault.AttributeFlags.Repackable; } + // overload, accept min/max quantities for loot items. + public ItemInfo(int definition, int minq = 1, int maxq = 1) : this() + { + Definition = definition; + MinQty = minq; + MaxQty = maxq; + Quantity = this.randomQuantity(); //randomize quantity for min-max ranged loots on init + Health = (float)EntityDefault.Health; + IsRepackaged = EntityDefault.AttributeFlags.Repackable; + } + + //Roll random + public int randomQuantity() + { + if(this.MinQty != this.MaxQty) + { + return FastRandom.NextInt(this.MinQty, this.MaxQty); + } + return this.MinQty; + } + public EntityDefault EntityDefault { get { return EntityDefault.Get(Definition); } diff --git a/src/Perpetuum/Items/Paint.cs b/src/Perpetuum/Items/Paint.cs new file mode 100644 index 000000000..9bfa9dce3 --- /dev/null +++ b/src/Perpetuum/Items/Paint.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using Perpetuum.Accounting.Characters; +using Perpetuum.Common.Loggers.Transaction; +using Perpetuum.Containers; +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.Robots; + +namespace Perpetuum.Items +{ + public class Paint : Item + { + private readonly RobotHelper _robotHelper; + public Paint(RobotHelper helper) + { + this._robotHelper = helper; + } + + public void Activate(RobotInventory targetContainer, Character character) + { + var robot = _robotHelper.LoadRobotForCharacter(targetContainer.GetOrLoadParentEntity().Eid, character, true); + robot.ThrowIfNull(ErrorCodes.NoRobotFound); + robot.DynamicProperties.Update(k.tint, this.ED.Config.Tint); //Apply color + robot.DynamicProperties.Update(k.decay, 255); //Reset Decay + robot.Save(); + } + + } +} \ No newline at end of file diff --git a/src/Perpetuum/Items/PropertyModifierCollection.cs b/src/Perpetuum/Items/PropertyModifierCollection.cs new file mode 100644 index 000000000..8428ce3d5 --- /dev/null +++ b/src/Perpetuum/Items/PropertyModifierCollection.cs @@ -0,0 +1,42 @@ +using Perpetuum.ExportedTypes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Items +{ + public class PropertyModifierCollection : IPropertyModifierCollection + { + public static readonly PropertyModifierCollection Empty = new PropertyModifierCollection(); + private readonly Dictionary modifiers = new Dictionary(); + + private PropertyModifierCollection() + { + + } + + public PropertyModifierCollection(IEnumerable modifiers) + { + this.modifiers = modifiers.ToDictionary(m => m.Field); + } + + public bool TryGetPropertyModifier(AggregateField field, out ItemPropertyModifier modifier) + { + return modifiers.TryGetValue(field, out modifier); + } + + public ItemPropertyModifier GetPropertyModifier(AggregateField field) + { + if (TryGetPropertyModifier(field, out ItemPropertyModifier m)) + { + return m; + } + + return ItemPropertyModifier.Create(field); + } + + public IEnumerable All => modifiers.Values; + } +} diff --git a/src/Perpetuum/Items/RespecToken.cs b/src/Perpetuum/Items/RespecToken.cs new file mode 100644 index 000000000..0ba18cd77 --- /dev/null +++ b/src/Perpetuum/Items/RespecToken.cs @@ -0,0 +1,41 @@ +using Perpetuum.Accounting; +using Perpetuum.Accounting.Characters; +using System; + +namespace Perpetuum.Items +{ + /// + /// Represents respec token item + /// + public class RespecToken : Item + { + public int OnceInMonths + { + get + { + return ED.Options.GetOption("onceInMonths"); + } + } + + public void Activate( + IAccountManager accountManager, + Account account, + Character character) + { + if (character.LastRespec.AddMonths(this.OnceInMonths) > DateTime.Now) + { + throw new PerpetuumException(ErrorCodes.RespecTokenIsNotReadyYet); + } + + var epData = accountManager.GetEPData(account, character); + + accountManager.FreeLockedEp(account, (int)epData[k.lockedEp]); + + var defaultExtensions = character.GetDefaultExtensions(); + + character.ResetAllExtensions(); + character.SetExtensions(defaultExtensions); + character.LastRespec = DateTime.Now; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Items/SparkTeleportDevice.cs b/src/Perpetuum/Items/SparkTeleportDevice.cs new file mode 100644 index 000000000..3ab15b923 --- /dev/null +++ b/src/Perpetuum/Items/SparkTeleportDevice.cs @@ -0,0 +1,13 @@ +namespace Perpetuum.Items +{ + public class SparkTeleportDevice : Item + { + public int BaseId + { + get + { + return ED.Options.GetOption("baseId"); + } + } + } +} diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index 9442a6e5e..d02553719 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -154,6 +154,7 @@ public class k public const string clear = "clear"; public const string client = "client"; public const string clientID = "clientID"; + public const string clientver = "clientVer"; public const string clockOffset = "clockOffset"; public const string clockStart = "clockStart"; public const string clockStop = "clockStop"; @@ -237,6 +238,7 @@ public class k public const string currentLoad = "currentLoad"; public const string currentRound = "currentRound"; public const string currentScore = "currentScore"; + public const string customDictionary = "customDictionary"; public const string cycle = "cycle"; public const string cycle_time = "cycle_time"; @@ -256,6 +258,9 @@ public class k public const string damageThermal = "damageThermal"; public const string damage_thermal = "damage_thermal"; + public const string damageToxic = "damageToxic"; + public const string damage_toxic = "damage_toxic"; + public const string damageDone = "damageDone"; public const string damageScale = "damageScale"; public const string damageTaken = "damageTaken"; @@ -366,6 +371,7 @@ public class k public const string extensionLevelRequired = "extensionLevelRequired"; public const string extensionRemoveAllowed = "extensionRemoveAllowed"; public const string extra = "extra"; + public const string extrapoints = "extrapoints"; public const string extractionType = "extractionType"; public const string fails = "fails"; public const string far = "far"; @@ -423,6 +429,7 @@ public class k public const string group = "group"; public const string groupEID = "groupEID"; public const string ground = "ground"; + public const string groundType = "groundType"; public const string growRate = "growRate"; public const string guid = "guid"; public const string hangar = "hangar"; @@ -809,6 +816,7 @@ public class k public const string publicProfile = "publicProfile"; public const string purchasable = "purchasable"; public const string purchaseCount = "purchaseCount"; + public const string prevZone = "prevZone"; public const string PVEAdmin = "PVEAdmin"; public const string pvpRemaining = "pvpRemaining"; @@ -998,7 +1006,9 @@ public class k public const string startedBy = "startedBy"; public const string startTime = "startTime"; public const string state = "state"; + public const string steambuildid = "steamBuildId"; public const string steps = "steps"; + public const string strongholdDespawnTime = "strongholdDespawnTime"; public const string structureEid = "structureEid"; public const string sub = "sub"; public const string submitInterval = "submitInterval"; @@ -1068,6 +1078,8 @@ public class k public const string threshold = "threshold"; public const string tickets = "tickets"; public const string tier = "tier"; + public const string tierType = "tiertype"; + public const string tierLevel = "tierlevel"; public const string time = "time"; public const string timeEfficiency = "timeEfficiency"; public const string timeExtensionPoints = "timeExtensionPoints"; @@ -1185,8 +1197,9 @@ public class k public const string email = "email"; + public const string emailConfirmed = "emailConfirmed"; + - //minerals public const string titan = "titan"; public const string crude = "crude"; @@ -1207,5 +1220,8 @@ public class k public const string es_teleport_column = "teleport_column"; public const string es_admin_trashcan = "admin_trashcan"; public const string es_account_bound = "account_bound"; + + // Definition of field turret + public const string TurretId = "turretId"; } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 7b3bc0412..fe02e47f1 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; using Perpetuum.Containers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; -using Perpetuum.Modules.Weapons; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Units; @@ -13,161 +10,74 @@ using Perpetuum.Zones.Beams; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Diagnostics; namespace Perpetuum.Modules { - public class ModuleProperty : ItemProperty + public abstract partial class ActiveModule : Module { - protected readonly Module module; - private List _effectModifiers; - - public ModuleProperty(Module module, AggregateField field) : base(field) - { - this.module = module; - } - - public void AddEffectModifier(AggregateField field) - { - if ( _effectModifiers == null ) - _effectModifiers = new List(); - - _effectModifiers.Add(field); - } - - protected override double CalculateValue() - { - var m = module.GetPropertyModifier(Field); - ApplyEffectModifiers(ref m); - return m.Value; - } - - protected void ApplyEffectModifiers(ref ItemPropertyModifier m) - { - if (_effectModifiers == null) - return; + private Lock _lock; + protected readonly ModuleProperty coreUsage; + protected readonly CycleTimeProperty cycleTime; + protected readonly ItemProperty falloff = ItemProperty.None; + protected readonly ModuleProperty optimalRange; - foreach (var effectModifier in _effectModifiers) - { - module.ParentRobot?.ApplyEffectPropertyModifiers(effectModifier,ref m); - } - } + private readonly CategoryFlags _ammoCategoryFlags; - protected override bool IsRelated(AggregateField field) + public Lock Lock { - if (_effectModifiers != null) + [CanBeNull] + get => _lock; + set { - if (_effectModifiers.Contains(field)) - return true; - } - - return base.IsRelated(field); - } - } - - public class CycleTimeProperty : ModuleProperty - { - private readonly ActiveModule _module; - - public CycleTimeProperty(ActiveModule module) : base(module, AggregateField.cycle_time) - { - _module = module; - } - - protected override double CalculateValue() - { - var cycleTime = _module.GetPropertyModifier(AggregateField.cycle_time); + if (_lock != null) + { + _lock.Changed -= LockChangedHandler; + } - var ammo = _module.GetAmmo(); - ammo?.ModifyCycleTime(ref cycleTime); + _lock = value; - var driller = _module as DrillerModule; - if (driller != null) - { - var miningAmmo = ammo as MiningAmmo; - if (miningAmmo != null) + if (_lock != null) { - miningAmmo.miningCycleTimeModifier.Update(); - var miningCycleTimeMod = miningAmmo.miningCycleTimeModifier.ToPropertyModifier(); - miningCycleTimeMod.Modify(ref cycleTime); + _lock.Changed += LockChangedHandler; } } - - ApplyEffectModifiers(ref cycleTime); - return cycleTime.Value; } - } - public class OptimalRangeProperty : ModuleProperty - { - private readonly ActiveModule _module; - - public OptimalRangeProperty(ActiveModule module) : base(module,AggregateField.optimal_range) - { - _module = module; - AddEffectModifier(AggregateField.effect_optimal_range_modifier); - } + public bool IsRanged { get; private set; } - protected override double CalculateValue() + public override double Volume { - var optimalRange = ItemPropertyModifier.Create(AggregateField.optimal_range); - - var ammo = _module.GetAmmo(); - var m = module as MissileWeaponModule; - if (m != null) + get { + double volume = base.Volume; + Items.Ammos.Ammo ammo = GetAmmo(); + if (ammo != null) { - optimalRange = ammo.OptimalRangePropertyModifier; - var missileRangeMod = m.MissileRangeModifier.ToPropertyModifier(); - missileRangeMod.Modify(ref optimalRange); - module.ApplyRobotPropertyModifiers(ref optimalRange); + volume += ammo.Volume; } - } - else - { - optimalRange = module.GetPropertyModifier(AggregateField.optimal_range); - ammo?.ModifyOptimalRange(ref optimalRange); - } - - ApplyEffectModifiers(ref optimalRange); - return optimalRange.Value; - } - } - - public class FalloffProperty : ModuleProperty - { - public FalloffProperty(Module module) : base(module, AggregateField.falloff) - { + return volume; + } } - protected override double CalculateValue() - { - var p = module.GetPropertyModifier(AggregateField.falloff); - - var ammo = ((ActiveModule) module).GetAmmo(); - ammo?.ModifyFalloff(ref p); + public TimeSpan CycleTime => TimeSpan.FromMilliseconds(cycleTime.Value); - return p.Value; - } - } + public double CoreUsage => coreUsage.Value; + public double OptimalRange => optimalRange.Value; - public abstract partial class ActiveModule : Module - { - private Lock _lock; - protected readonly ModuleProperty coreUsage; - protected readonly CycleTimeProperty cycleTime; - private readonly ItemProperty _falloff = ItemProperty.None; - protected readonly ModuleProperty optimalRange; + public double Falloff => falloff.Value; - private readonly CategoryFlags _ammoCategoryFlags; - - protected ActiveModule(CategoryFlags ammoCategoryFlags,bool ranged = false) + protected ActiveModule(CategoryFlags ammoCategoryFlags, bool ranged = false) { IsRanged = ranged; - coreUsage = new ModuleProperty(this,AggregateField.core_usage); + coreUsage = new ModuleProperty(this, AggregateField.core_usage); AddProperty(coreUsage); cycleTime = new CycleTimeProperty(this); AddProperty(cycleTime); @@ -176,13 +86,17 @@ protected ActiveModule(CategoryFlags ammoCategoryFlags,bool ranged = false) { optimalRange = new OptimalRangeProperty(this); AddProperty(optimalRange); - _falloff = new FalloffProperty(this); - AddProperty(_falloff); + falloff = new FalloffProperty(this); + AddProperty(falloff); } _ammoCategoryFlags = ammoCategoryFlags; } + protected ActiveModule(bool ranged) : this(CategoryFlags.undefined, ranged) + { + } + public override void Initialize() { InitState(); @@ -190,141 +104,115 @@ public override void Initialize() base.Initialize(); } - - protected ActiveModule(bool ranged) : this(CategoryFlags.undefined,ranged) + public override void AcceptVisitor(IEntityVisitor visitor) { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } - public TimeSpan CycleTime + public override void UpdateRelatedProperties(AggregateField field) { - get { return TimeSpan.FromMilliseconds(cycleTime.Value); } + Items.Ammos.Ammo ammo = GetAmmo(); + + ammo?.UpdateRelatedProperties(field); + base.UpdateRelatedProperties(field); } - public double CoreUsage + public bool IsInRange(Position position) { - get { return coreUsage.Value; } + Debug.Assert(ParentRobot != null, "ParentRobot != null"); + + return !IsRanged || ParentRobot.IsInRangeOf3D(position, OptimalRange + Falloff); } - public double OptimalRange + public void ForceUpdate() { - get { return optimalRange.Value; } + SendModuleStateToPlayer(); + SendAmmoUpdatePacketToPlayer(); } - protected double Falloff + public void Update(TimeSpan time) { - get { return _falloff.Value; } + _states.Update(time); } - public Lock Lock + public override void UpdateAllProperties() { - [CanBeNull] private get { return _lock; } - set - { - if (_lock != null) - _lock.Changed -= LockChangedHandler; + base.UpdateAllProperties(); - _lock = value; + Items.Ammos.Ammo ammo = GetAmmo(); - if (_lock != null) - _lock.Changed += LockChangedHandler; - } + ammo?.UpdateAllProperties(); } - public bool IsRanged { get; private set; } - - public override void AcceptVisitor(IEntityVisitor visitor) + public override void Unequip(Container container) { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); + _ = UnequipAmmoToContainer(container); + base.Unequip(container); } - private void LockChangedHandler(Lock @lock) + public override Dictionary ToDictionary() { - if (State.Type == ModuleStateType.Idle || State.Type == ModuleStateType.AmmoLoad) - return; + Dictionary result = base.ToDictionary(); - var shutdown = @lock.State == LockState.Disabled || (ED.AttributeFlags.PrimaryLockedTarget && !@lock.Primary); - if (!shutdown) - return; + result.Add(k.ammoCategoryFlags, (long)_ammoCategoryFlags); - State.SwitchTo(ModuleStateType.Shutdown); - _lock = null; - } + Items.Ammos.Ammo ammo = GetAmmo(); - private bool IsInRange(Position position) - { - Debug.Assert(ParentRobot != null, "ParentRobot != null"); - return !IsRanged || ParentRobot.IsInRangeOf3D(position,OptimalRange + Falloff); - } + if (ammo == null) + { + return result; + } - public void ForceUpdate() - { - SendModuleStateToPlayer(); - SendAmmoUpdatePacketToPlayer(); - } + result.Add(k.ammo, ammo.ToDictionary()); + result.Add(k.ammoQuantity, ammo.Quantity); - private BeamType GetBeamType() - { - var ammo = GetAmmo(); - return ammo != null ? BeamHelper.GetBeamByDefinition(ammo.Definition) : BeamHelper.GetBeamByDefinition(Definition); + return result; } - private void SendModuleStateToPlayer() - { - var state = State; - - var player = ParentRobot as Player; - if (player == null) - return; - - var packet = new Packet(ZoneCommand.ModuleChangeState); - Debug.Assert(ParentComponent != null, "ParentComponent != null"); - packet.AppendByte((byte)ParentComponent.Type); - packet.AppendByte((byte) Slot); - packet.AppendByte((byte)state.Type); + protected abstract void OnAction(); - var timed = state as ITimedModuleState; - if (timed == null) + protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) + { + Player targetPlayer = unitLockTarget.Target as Player; + if (targetPlayer is null && + ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player player) { - packet.AppendInt(0); - packet.AppendInt(0); + parentPlayer = player; } - else + + if (parentPlayer != null && targetPlayer != null) { - packet.AppendInt((int)timed.Timer.Interval.TotalMilliseconds); - packet.AppendInt((int)timed.Timer.Elapsed.TotalMilliseconds); + _ = ((unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError()); + _ = targetPlayer.CheckPvp().ThrowIfError(); } - - player.Session.SendPacket(packet); - } - - public void Update(TimeSpan time) - { - _states.Update(time); } - protected abstract void OnAction(); - protected Lock GetLock() { - var currentLock = Lock.ThrowIfNull(ErrorCodes.LockTargetNotFound); - currentLock.State.ThrowIfNotEqual(LockState.Locked, ErrorCodes.LockIsInProgress); + Lock currentLock = Lock.ThrowIfNull(ErrorCodes.LockTargetNotFound); + + _ = currentLock.State.ThrowIfNotEqual(LockState.Locked, ErrorCodes.LockIsInProgress); - var unitLockTarget = currentLock as UnitLock; - if (unitLockTarget != null) + if (currentLock is UnitLock unitLockTarget) { IsInRange(unitLockTarget.Target.CurrentPosition).ThrowIfFalse(ErrorCodes.TargetOutOfRange); - var parentPlayer = ParentRobot as Player; + Player parentPlayer = ParentRobot as Player; - if (ED.AttributeFlags.OffensiveModule) + if (parentPlayer is null && + ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player player) { - if ((parentPlayer != null)) - { - // pvp ellenorzes - (unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError(); - } + parentPlayer = player; + } + if (ED.AttributeFlags.OffensiveModule) + { + HandleOffensivePVPCheck(parentPlayer, unitLockTarget); Debug.Assert(ParentRobot != null, "ParentRobot != null"); ParentRobot.OnAggression(unitLockTarget.Target); } @@ -335,8 +223,8 @@ protected Lock GetLock() } else { - var terrainLockTarget = currentLock as TerrainLock; - if (terrainLockTarget != null) + ; + if (currentLock is TerrainLock terrainLockTarget) { IsInRange(terrainLockTarget.Location).ThrowIfFalse(ErrorCodes.TargetOutOfRange); } @@ -345,17 +233,9 @@ protected Lock GetLock() return currentLock; } - public override void UpdateAllProperties() - { - base.UpdateAllProperties(); - - var ammo = GetAmmo(); - ammo?.UpdateAllProperties(); - } - protected void CreateBeam(Unit target, BeamState beamState) { - CreateBeam(target, beamState, 0, 0, 0); + _ = CreateBeam(target, beamState, 0, 0, 0); } protected int CreateBeam(Unit target, BeamState beamState, int duration, double bulletTime) @@ -363,72 +243,14 @@ protected int CreateBeam(Unit target, BeamState beamState, int duration, double return CreateBeam(target, beamState, duration, bulletTime, 0); } - private int CreateBeam(Unit target, BeamState beamState, int duration, double bulletTime,int visibility) - { - var delay = 0; - var beamType = GetBeamType(); - - if (beamType <= 0) - return delay; - - delay = BeamHelper.GetBeamDelay(beamType); - - if (duration == 0) - duration = (int)CycleTime.TotalMilliseconds; - - Debug.Assert(ParentComponent != null, "ParentComponent != null"); - var slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 - - var builder = Beam.NewBuilder().WithType(beamType) - .WithSlot(slot) - .WithSource(ParentRobot) - .WithState(beamState) - .WithBulletTime(bulletTime) - .WithDuration(duration) - .WithTarget(target) - .WithVisibility(visibility); - - Zone.CreateBeam(builder); - return delay; - } - protected void CreateBeam(Position location, BeamState beamState) { - CreateBeam(location, beamState, 0, 0, 0); + _ = CreateBeam(location, beamState, 0, 0, 0); } protected int CreateBeam(Position location, BeamState beamState, int duration, double bulletTime) { - return CreateBeam(location, beamState, duration, bulletTime,0); - } - - private int CreateBeam(Position location, BeamState beamState, int duration, double bulletTime,int visibility) - { - var delay = 0; - var beamType = GetBeamType(); - - if (beamType <= 0) - return delay; - - delay = BeamHelper.GetBeamDelay(beamType); - - if (duration == 0) - duration = (int) CycleTime.TotalMilliseconds; - - Debug.Assert(ParentComponent != null, "ParentComponent != null"); - var slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 - - var builder = Beam.NewBuilder().WithType(beamType) - .WithSlot(slot) - .WithSource(ParentRobot) - .WithState(beamState) - .WithBulletTime(bulletTime) - .WithDuration(duration) - .WithTargetPosition(location) - .WithVisibility(visibility); - - Zone.CreateBeam(builder); - return delay; + return CreateBeam(location, beamState, duration, bulletTime, 0); } protected override void OnUpdateProperty(AggregateField field) @@ -437,83 +259,95 @@ protected override void OnUpdateProperty(AggregateField field) { case AggregateField.core_usage: case AggregateField.effect_core_usage_gathering_modifier: - { - coreUsage.Update(); - break; - } + { + coreUsage.Update(); + + break; + } case AggregateField.cycle_time: case AggregateField.effect_weapon_cycle_time_modifier: case AggregateField.effect_gathering_cycle_time_modifier: - { - cycleTime.Update(); - break; - } + { + cycleTime.Update(); + + break; + } case AggregateField.optimal_range: case AggregateField.effect_optimal_range_modifier: case AggregateField.effect_ew_optimal_range_modifier: case AggregateField.module_missile_range_modifier: case AggregateField.effect_missile_range_modifier: - { - optimalRange.Update(); - break; - } + { + optimalRange.Update(); + + break; + } case AggregateField.falloff: - { - _falloff.Update(); - break; - } + { + falloff.Update(); + + break; + } } base.OnUpdateProperty(field); } - public override void Unequip(Container container) - { - UnequipAmmoToContainer(container); - base.Unequip(container); - } - - protected double ModifyValueByOptimalRange(Unit target,double value) + protected double ModifyValueByOptimalRange(Unit target, double value) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); - var distance = ParentRobot.GetDistance(target); + double distance = ParentRobot.GetDistance(target); + if (distance <= OptimalRange) + { return value; + } if (distance > OptimalRange + Falloff) + { return 0.0; + } + + double x = (distance - OptimalRange) / Falloff; + double m = (Math.Cos(x * Math.PI) / 2) + 0.5; - var x = (distance - OptimalRange) / Falloff; - var m = Math.Cos(x * Math.PI) / 2 + 0.5; return value * m; } protected LOSResult GetLineOfSight(Unit target) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); - var visibility = ParentRobot.GetVisibility(target); + + IUnitVisibility visibility = ParentRobot.GetVisibility(target); + return visibility?.GetLineOfSight(IsCategory(CategoryFlags.cf_missiles)) ?? LOSResult.None; } protected LOSResult GetLineOfSight(Position location) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); - var losResult = ParentRobot.Zone.IsInLineOfSight(ParentRobot,location,IsCategory(CategoryFlags.cf_missiles)); + + LOSResult losResult = ParentRobot.Zone.IsInLineOfSight(ParentRobot, location, IsCategory(CategoryFlags.cf_missiles)); + return losResult; } protected bool LOSCheckAndCreateBeam(Unit target) { - var result = GetLineOfSight(target); + LOSResult result = GetLineOfSight(target); + if (result.hit) { - var beamState = (result.blockingFlags != BlockingFlags.Undefined) ? BeamState.AlignToTerrain : BeamState.Hit; + BeamState beamState = (result.blockingFlags != BlockingFlags.Undefined) ? BeamState.AlignToTerrain : BeamState.Hit; + CreateBeam(result.position, beamState); + return false; } CreateBeam(target, BeamState.Hit); + return true; } @@ -522,52 +356,141 @@ protected void OnError(ErrorCodes error) SendModuleErrorToPlayer(error); } - private void SendModuleErrorToPlayer(ErrorCodes error) + private void LockChangedHandler(Lock @lock) + { + if (State.Type == ModuleStateType.Idle || State.Type == ModuleStateType.AmmoLoad) + { + return; + } + + bool shutdown = @lock.State == LockState.Disabled || (ED.AttributeFlags.PrimaryLockedTarget && !@lock.Primary); + + if (!shutdown) + { + return; + } + + State.SwitchTo(ModuleStateType.Shutdown); + _lock = null; + } + + private BeamType GetBeamType() + { + Items.Ammos.Ammo ammo = GetAmmo(); + + return ammo != null + ? BeamHelper.GetBeamByDefinition(ammo.Definition) + : BeamHelper.GetBeamByDefinition(Definition); + } + + private void SendModuleStateToPlayer() { - var player = ParentRobot as Player; - if (player == null) + IModuleState state = State; + + if (!(ParentRobot is Player player)) + { return; + } + + Packet packet = new Packet(ZoneCommand.ModuleChangeState); + + Debug.Assert(ParentComponent != null, "ParentComponent != null"); + packet.AppendByte((byte)ParentComponent.Type); + packet.AppendByte((byte)Slot); + packet.AppendByte((byte)state.Type); + + if (!(state is ITimedModuleState timed)) + { + packet.AppendInt(0); + packet.AppendInt(0); + } + else + { + packet.AppendInt((int)timed.Timer.Interval.TotalMilliseconds); + packet.AppendInt((int)timed.Timer.Elapsed.TotalMilliseconds); + } - var packet = new CombatLogPacket(error, this, _lock); player.Session.SendPacket(packet); } - public override Dictionary ToDictionary() + private int CreateBeam(Unit target, BeamState beamState, int duration, double bulletTime, int visibility) { - var result = base.ToDictionary(); + int delay = 0; + BeamType beamType = GetBeamType(); - result.Add(k.ammoCategoryFlags, (long)_ammoCategoryFlags); + if (beamType <= 0) + { + return delay; + } - var ammo = GetAmmo(); - if (ammo == null) - return result; + delay = BeamHelper.GetBeamDelay(beamType); - result.Add(k.ammo, ammo.ToDictionary()); - result.Add(k.ammoQuantity, ammo.Quantity); + if (duration == 0) + { + duration = (int)CycleTime.TotalMilliseconds; + } - return result; + Debug.Assert(ParentComponent != null, "ParentComponent != null"); + + int slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 + BeamBuilder builder = Beam.NewBuilder().WithType(beamType) + .WithSlot(slot) + .WithSource(ParentRobot) + .WithState(beamState) + .WithBulletTime(bulletTime) + .WithDuration(duration) + .WithTarget(target) + .WithVisibility(visibility); + + Zone.CreateBeam(builder); + + return delay; } - public override double Volume + private int CreateBeam(Position location, BeamState beamState, int duration, double bulletTime, int visibility) { - get + int delay = 0; + BeamType beamType = GetBeamType(); + + if (beamType <= 0) { - var volume = base.Volume; + return delay; + } - var ammo = GetAmmo(); - if (ammo != null) - volume += ammo.Volume; + delay = BeamHelper.GetBeamDelay(beamType); - return volume; + if (duration == 0) + { + duration = (int)CycleTime.TotalMilliseconds; } + + Debug.Assert(ParentComponent != null, "ParentComponent != null"); + + int slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 + BeamBuilder builder = Beam.NewBuilder().WithType(beamType) + .WithSlot(slot) + .WithSource(ParentRobot) + .WithState(beamState) + .WithBulletTime(bulletTime) + .WithDuration(duration) + .WithTargetPosition(location) + .WithVisibility(visibility); + + Zone.CreateBeam(builder); + + return delay; } - public override void UpdateRelatedProperties(AggregateField field) + private void SendModuleErrorToPlayer(ErrorCodes error) { - var ammo = GetAmmo(); - ammo?.UpdateRelatedProperties(field); + if (!(ParentRobot is Player player)) + { + return; + } - base.UpdateRelatedProperties(field); + CombatLogPacket packet = new CombatLogPacket(error, this, _lock); + + player.Session.SendPacket(packet); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/ArmorRepairModule.cs b/src/Perpetuum/Modules/ArmorRepairModule.cs index 05d67bb57..cbd32bace 100644 --- a/src/Perpetuum/Modules/ArmorRepairModule.cs +++ b/src/Perpetuum/Modules/ArmorRepairModule.cs @@ -1,9 +1,10 @@ using System; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -27,6 +28,7 @@ public override void UpdateProperty(AggregateField field) case AggregateField.effect_repair_amount_modifier: { armorRepairAmount.Update(); + return; } } @@ -36,14 +38,18 @@ public override void UpdateProperty(AggregateField field) protected void OnRepair(Unit target, double amount) { - if (amount <= 0.0) + if (amount <= 0.0) + { return; + } var armor = target.Armor; + target.Armor += amount; - var total = Math.Abs(armor - target.Armor); + var total = Math.Abs(armor - target.Armor); var packet = new CombatLogPacket(CombatLogType.ArmorRepair, target, ParentRobot, this); + packet.AppendDouble(amount); packet.AppendDouble(total); packet.Send(target, ParentRobot); @@ -59,15 +65,20 @@ public ArmorRepairModule() : base(false) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void OnAction() { var amount = armorRepairAmount.Value; - OnRepair(ParentRobot,amount); - ParentRobot.SpreadAssistThreatToNpcs(ParentRobot,new Threat(ThreatType.Support,amount)); + OnRepair(ParentRobot, amount); + + var threatAmount = Math.Sqrt(amount).Clamp(1, 100); + + ParentRobot.SpreadAssistThreatToNpcs(ParentRobot, new Threat(ThreatType.Support, threatAmount)); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs index e564b8693..354094c76 100644 --- a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs +++ b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs @@ -2,6 +2,8 @@ using System.Linq; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -61,10 +63,26 @@ protected override void OnAction() if (zone == null) return; - var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + Position? lockPosition = null; - zone.Units.OfType().WithinRange(terrainLock.Location, BLOB_EMITTER_DEPLOY_RANGE).Any().ThrowIfTrue(ErrorCodes.BlobEmitterInRange); - var targetPosition = terrainLock.Location.AddToZ(BLOB_EMITTER_HEIGHT); + var myLock = GetLock(); + if (myLock is TerrainLock) + { + lockPosition = (myLock as TerrainLock).Location.AddToZ(BLOB_EMITTER_HEIGHT); + } + else if (myLock is UnitLock) + { + lockPosition = (myLock as UnitLock).Target.CurrentPosition.AddToZ(BLOB_EMITTER_HEIGHT); + } + else + { + OnError(ErrorCodes.InvalidLockType); + return; + } + + Position targetPosition = lockPosition.Value; + + zone.Units.OfType().WithinRange(targetPosition, BLOB_EMITTER_DEPLOY_RANGE).Any().ThrowIfTrue(ErrorCodes.BlobEmitterInRange); var r = zone.IsInLineOfSight(ParentRobot, targetPosition, false); if (r.hit) { @@ -79,6 +97,8 @@ protected override void OnAction() blobEmitter.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); blobEmitter.BlobEmission = _blobEmission.Value; blobEmitter.BlobEmissionRadius = _blobEmissionRadius.Value; + if (ParentRobot is Player player) + blobEmitter.Owner = player.Character.Eid; var finder = new ClosestWalkablePositionFinder(zone, targetPosition); var position = finder.FindOrThrow(); @@ -88,7 +108,7 @@ protected override void OnAction() .WithPosition(targetPosition) .WithDuration(TimeSpan.FromSeconds(5)); - blobEmitter.AddToZone(zone,position,ZoneEnterType.Default, beamBuilder); + blobEmitter.AddToZone(zone, position, ZoneEnterType.Default, beamBuilder); ConsumeAmmo(); } diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index e87ed07ae..8429f26a1 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -1,63 +1,53 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Transactions; using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Zones; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains; using Perpetuum.Zones.Terrains.Materials; using Perpetuum.Zones.Terrains.Materials.Minerals; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Transactions; namespace Perpetuum.Modules { public class DrillerModule : GathererModule { + private const int MAX_EP_PER_DAY = 1440; private readonly RareMaterialHandler _rareMaterialHandler; private readonly MaterialHelper _materialHelper; private readonly ItemProperty _miningAmountModifier; + private readonly ISet LIQUIDS = new HashSet(new int[] + { + (int)MaterialType.Crude, + (int)MaterialType.Liquizit, + (int)MaterialType.Epriton + }); - public DrillerModule(CategoryFlags ammoCategoryFlags,RareMaterialHandler rareMaterialHandler,MaterialHelper materialHelper) : base(ammoCategoryFlags, true) + public DrillerModule(CategoryFlags ammoCategoryFlags, RareMaterialHandler rareMaterialHandler, MaterialHelper materialHelper) + : base(ammoCategoryFlags, true) { _rareMaterialHandler = rareMaterialHandler; _materialHelper = materialHelper; _miningAmountModifier = new MiningAmountModifierProperty(this); - AddProperty(_miningAmountModifier); + AddProperty(_miningAmountModifier); } public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); - } - - private class MiningAmountModifierProperty : ModuleProperty - { - private readonly DrillerModule _module; - - public MiningAmountModifierProperty(DrillerModule module) : base(module, AggregateField.mining_amount_modifier) { - _module = module; - } - - protected override double CalculateValue() - { - if (module.ParentRobot == null) - return 1.0; - - var m = module.ParentRobot.GetPropertyModifier(AggregateField.mining_amount_modifier); - - var ammo = (MiningAmmo)_module.GetAmmo(); - ammo?.ApplyMiningAmountModifier(ref m); - module.ParentRobot?.ApplyEffectPropertyModifiers(AggregateField.effect_mining_amount_modifier,ref m); - - return m.Value; + base.AcceptVisitor(visitor); } } @@ -69,6 +59,7 @@ public override void UpdateProperty(AggregateField field) case AggregateField.effect_mining_amount_modifier: { _miningAmountModifier.Update(); + return; } } @@ -76,77 +67,126 @@ public override void UpdateProperty(AggregateField field) base.UpdateProperty(field); } + public List Extract(MineralLayer layer, Point location, uint amount) + { + if (!layer.HasMineral(location)) + { + return new List(); + } + + MineralExtractor extractor = new MineralExtractor(location, amount, _materialHelper); + + layer.AcceptVisitor(extractor); + + return new List(extractor.Items); + } + protected override void OnAction() { - var zone = Zone; - if ( zone != null ) + IZone zone = Zone; + + if (zone != null) + { DoExtractMinerals(zone); + } ConsumeAmmo(); } - public List Extract(MineralLayer layer, Point location, uint amount) + protected override int CalculateEp(int materialType) { - if (!layer.HasMineral(location)) - return new List(); + DrillerModule[] activeGathererModules = this is RemoteControlledDrillerModule + ? ParentRobot.ActiveModules.OfType().Where(m => m.State.Type != ModuleStateType.Idle).ToArray() + : ParentRobot.ActiveModules.OfType().Where(m => m.State.Type != ModuleStateType.Idle).ToArray(); - var extractor = new MineralExtractor(location, amount,_materialHelper); - layer.AcceptVisitor(extractor); - return new List(extractor.Items); + if (activeGathererModules.Length == 0) + { + return 0; + } + + TimeSpan avgCycleTime = activeGathererModules.Select(m => m.CycleTime).Average(); + TimeSpan t = TimeSpan.FromDays(1).Divide(avgCycleTime); + double chance = (double)MAX_EP_PER_DAY / t.Ticks; + + chance /= activeGathererModules.Length; + + if (LIQUIDS.Contains(materialType)) + { + chance /= 2.0; + } + + double rand = FastRandom.NextDouble(); + + return rand <= chance ? 1 : 0; } - private void DoExtractMinerals(IZone zone) + public void DoExtractMinerals(IZone zone) { - var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + TerrainLock terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); - var ammo = GetAmmo() as MiningAmmo; - if (ammo == null) - return; + MaterialType materialType; - var materialInfo = _materialHelper.GetMaterialInfo(ammo.MaterialType); + if (ParentRobot is RemoteControlledCreature) + { + materialType = zone.Terrain.GetMaterialTypeAtPosition(terrainLock.Location); + } + else + { + if (!(GetAmmo() is MiningAmmo ammo)) + { + return; + } - CheckEnablerEffect(materialInfo); + materialType = ammo.MaterialType; + } + + MaterialInfo materialInfo = _materialHelper.GetMaterialInfo(materialType); - var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); - var materialAmount = materialInfo.Amount * _miningAmountModifier.Value; + CheckEnablerEffect(materialInfo); - var extractedMaterials = Extract(mineralLayer, terrainLock.Location,(uint) materialAmount); - extractedMaterials.Count.ThrowIfEqual(0, ErrorCodes.NoMineralOnTile); + MineralLayer mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); + double materialAmount = materialInfo.Amount * _miningAmountModifier.Value; + List extractedMaterials = Extract(mineralLayer, terrainLock.Location, (uint)materialAmount); + _ = extractedMaterials.Count.ThrowIfEqual(0, ErrorCodes.NoMineralOnTile); extractedMaterials.AddRange(_rareMaterialHandler.GenerateRareMaterials(materialInfo.EntityDefault.Definition)); CreateBeam(terrainLock.Location, BeamState.AlignToTerrain); - using (var scope = Db.CreateTransaction()) + using (TransactionScope scope = Db.CreateTransaction()) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); - var container = ParentRobot.GetContainer(); + + Robots.RobotInventory container = ParentRobot.GetContainer(); + Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); - var player = ParentRobot as Player; - Debug.Assert(player != null,"player != null"); + Player player = ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer + : ParentRobot as Player; + + Debug.Assert(player != null, "player != null"); - foreach (var material in extractedMaterials) + foreach (ItemInfo material in extractedMaterials) { - var item = (Item)Factory.CreateWithRandomEID(material.Definition); + Item item = (Item)Factory.CreateWithRandomEID(material.Definition); item.Owner = Owner; item.Quantity = material.Quantity; - container.AddItem(item, true); - var drilledMineralDefinition = material.Definition; - var drilledQuantity = material.Quantity; + int drilledMineralDefinition = material.Definition; + int drilledQuantity = material.Quantity; - player.MissionHandler.EnqueueMissionEventInfo(new DrillMineralEventInfo(player,drilledMineralDefinition,drilledQuantity,terrainLock.Location)); - player.Zone?.MiningLogHandler.EnqueueMiningLog(drilledMineralDefinition,drilledQuantity); + player.MissionHandler.EnqueueMissionEventInfo(new DrillMineralEventInfo(player, drilledMineralDefinition, drilledQuantity, terrainLock.Location)); + player.Zone?.MiningLogHandler.EnqueueMiningLog(drilledMineralDefinition, drilledQuantity); } //save container container.Save(); - OnGathererMaterial(zone, player); - + OnGathererMaterial(zone, player, (int)materialInfo.Type); Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); } @@ -154,13 +194,20 @@ private void DoExtractMinerals(IZone zone) private void CheckEnablerEffect(MaterialInfo materialInfo) { - if ( !Zone.Configuration.Terraformable ) + if (!Zone.Configuration.Terraformable) + { return; + } - if (!materialInfo.EnablerExtensionRequired) + if (!materialInfo.EnablerExtensionRequired) + { return; + } - var containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); + bool containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect) || + (ParentRobot is RemoteControlledCreature rcu && + rcu.CommandRobot is Player player && + player.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect)); containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); } } diff --git a/src/Perpetuum/Modules/EffectModules/DetectionModule.cs b/src/Perpetuum/Modules/EffectModules/DetectionModule.cs index 8d6d29f9d..59e52b90d 100644 --- a/src/Perpetuum/Modules/EffectModules/DetectionModule.cs +++ b/src/Perpetuum/Modules/EffectModules/DetectionModule.cs @@ -1,5 +1,6 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Zones.Effects; namespace Perpetuum.Modules.EffectModules diff --git a/src/Perpetuum/Modules/EffectModules/EffectModule.cs b/src/Perpetuum/Modules/EffectModules/EffectModule.cs index 1e81cb408..2e4471e94 100644 --- a/src/Perpetuum/Modules/EffectModules/EffectModule.cs +++ b/src/Perpetuum/Modules/EffectModules/EffectModule.cs @@ -1,4 +1,5 @@ -using Perpetuum.StateMachines; +using Perpetuum.EntityFramework; +using Perpetuum.StateMachines; using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.Locking.Locks; @@ -66,6 +67,12 @@ protected virtual bool CanApplyEffect(Unit target) return true; } + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + base.AcceptVisitor(visitor); + } + protected virtual void OnApplyingEffect(Unit target) {} protected abstract void SetupEffect(EffectBuilder effectBuilder); diff --git a/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs b/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs new file mode 100644 index 000000000..bc0f9a5e1 --- /dev/null +++ b/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs @@ -0,0 +1,32 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Zones.Effects; + +namespace Perpetuum.Modules.EffectModules +{ + public class MineDetectorModule : EffectModule + { + private readonly ItemProperty mineDetectionRange; + + public MineDetectorModule() + { + mineDetectionRange = new ModuleProperty(this, AggregateField.effect_mine_detection_range_modifier); + AddProperty(mineDetectionRange); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + base.AcceptVisitor(visitor); + } + + protected override void SetupEffect(EffectBuilder effectBuilder) + { + effectBuilder + .SetType(EffectType.effect_mine_detector) + .WithPropertyModifier(mineDetectionRange.ToPropertyModifier()); + } + } +} diff --git a/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs b/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs index 1df2a68b0..cf500cc5e 100644 --- a/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs @@ -1,8 +1,10 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -22,9 +24,12 @@ public RemoteSensorBoosterModule() : base(true) protected override bool CanApplyEffect(Unit target) { if (!ParentIsPlayer() || !(target is Npc)) + { return true; + } OnError(ErrorCodes.ThisModuleIsNotSupportedOnNPCs); + return false; } @@ -35,10 +40,11 @@ protected override void OnApplyingEffect(Unit target) protected override void SetupEffect(EffectBuilder effectBuilder) { - effectBuilder.SetType(EffectType.effect_remote_sensor_boost) - .SetSource(ParentRobot) - .WithPropertyModifier(_effectSensorBoosterLockingRangeModifier.ToPropertyModifier()) - .WithPropertyModifier(_effectSensorBoosterLockingTimeModifier.ToPropertyModifier()); + effectBuilder + .SetType(EffectType.effect_remote_sensor_boost) + .SetSource(ParentRobot) + .WithPropertyModifier(_effectSensorBoosterLockingRangeModifier.ToPropertyModifier()) + .WithPropertyModifier(_effectSensorBoosterLockingTimeModifier.ToPropertyModifier()); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs b/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs index aae306ea3..6d37943bc 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs @@ -1,9 +1,10 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -23,14 +24,17 @@ public SensorBoosterModule() public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void SetupEffect(EffectBuilder effectBuilder) { - effectBuilder.SetType(EffectType.effect_sensor_boost) - .WithPropertyModifier(_effectSensorBoosterLockingRangeModifier.ToPropertyModifier()) - .WithPropertyModifier(_effectSensorBoosterLockingTimeModifier.ToPropertyModifier()); + effectBuilder + .SetType(EffectType.effect_sensor_boost) + .WithPropertyModifier(_effectSensorBoosterLockingRangeModifier.ToPropertyModifier()) + .WithPropertyModifier(_effectSensorBoosterLockingTimeModifier.ToPropertyModifier()); } diff --git a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs index 26dd205b6..1c4589c32 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -1,9 +1,10 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -16,7 +17,6 @@ public class SensorDampenerModule : EffectModule public SensorDampenerModule() : base(true) { optimalRange.AddEffectModifier(AggregateField.effect_ew_optimal_range_modifier); - _ecmStrength = new ModuleProperty(this,AggregateField.ecm_strength); AddProperty(_ecmStrength); _effectSensorDampenerLockingRangeModifier = new ModuleProperty(this, AggregateField.effect_sensor_dampener_locking_range_modifier); @@ -28,31 +28,38 @@ public SensorDampenerModule() : base(true) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override bool CanApplyEffect(Unit target) { - var sensorStrength = target.SensorStrength * FastRandom.NextDouble(); - sensorStrength = ModifyValueByOptimalRange(target, sensorStrength); + var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble(); + var rangedEcmStrength = ModifyValueByOptimalRange(target, _ecmStrength.Value); - if (sensorStrength < _ecmStrength.Value) + if (targetSensorStrength < rangedEcmStrength) + { return true; + } OnError(ErrorCodes.AccuracyCheckFailed); + return false; } protected override void OnApplyingEffect(Unit target) { - target.AddThreat(ParentRobot, new Threat(ThreatType.Debuff, Threat.SENSOR_DAMPENER)); + target.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); } protected override void SetupEffect(EffectBuilder effectBuilder) { - effectBuilder.SetType(EffectType.effect_sensor_supress).SetSource(ParentRobot) - .WithPropertyModifier(_effectSensorDampenerLockingRangeModifier.ToPropertyModifier()) - .WithPropertyModifier(_effectSensorDampenerLockingTimeModifier.ToPropertyModifier()); + effectBuilder + .SetType(EffectType.effect_sensor_supress) + .SetSource(ParentRobot) + .WithPropertyModifier(_effectSensorDampenerLockingRangeModifier.ToPropertyModifier()) + .WithPropertyModifier(_effectSensorDampenerLockingTimeModifier.ToPropertyModifier()); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/EffectModules/ShieldGeneratorModule.cs b/src/Perpetuum/Modules/EffectModules/ShieldGeneratorModule.cs index 04d29bb52..9cf4c2066 100644 --- a/src/Perpetuum/Modules/EffectModules/ShieldGeneratorModule.cs +++ b/src/Perpetuum/Modules/EffectModules/ShieldGeneratorModule.cs @@ -2,6 +2,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Zones.Effects; namespace Perpetuum.Modules.EffectModules diff --git a/src/Perpetuum/Modules/EffectModules/StealthModule.cs b/src/Perpetuum/Modules/EffectModules/StealthModule.cs index 0762cab19..fbd6d6723 100644 --- a/src/Perpetuum/Modules/EffectModules/StealthModule.cs +++ b/src/Perpetuum/Modules/EffectModules/StealthModule.cs @@ -1,5 +1,6 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Zones.Effects; namespace Perpetuum.Modules.EffectModules diff --git a/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs b/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs new file mode 100644 index 000000000..d90f0df7a --- /dev/null +++ b/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs @@ -0,0 +1,32 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Zones.Effects; + +namespace Perpetuum.Modules.EffectModules +{ + public class TargetBlinderModule : EffectModule + { + private readonly ItemProperty _effectDetectionStrengthModifier; + + public TargetBlinderModule() : base(true) + { + _effectDetectionStrengthModifier = new ModuleProperty(this, AggregateField.effect_detection_strength_modifier); + AddProperty(_effectDetectionStrengthModifier); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + base.AcceptVisitor(visitor); + } + + protected override void SetupEffect(EffectBuilder effectBuilder) + { + effectBuilder.SetType(EffectType.effect_detection) + .SetSource(ParentRobot) + .WithPropertyModifier(_effectDetectionStrengthModifier.ToPropertyModifier()); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs b/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs index bf219b96c..c1c690c87 100644 --- a/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs @@ -1,5 +1,7 @@ -using Perpetuum.ExportedTypes; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Zones.Effects; namespace Perpetuum.Modules.EffectModules @@ -14,6 +16,12 @@ public TargetPainterModule() : base(true) AddProperty(_effectStealthStrengthModifier); } + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + base.AcceptVisitor(visitor); + } + protected override void SetupEffect(EffectBuilder effectBuilder) { effectBuilder.SetType(EffectType.effect_target_painting) diff --git a/src/Perpetuum/Modules/EffectModules/WebberModule.cs b/src/Perpetuum/Modules/EffectModules/WebberModule.cs index d3c9ecd54..ec0046cf7 100644 --- a/src/Perpetuum/Modules/EffectModules/WebberModule.cs +++ b/src/Perpetuum/Modules/EffectModules/WebberModule.cs @@ -1,9 +1,10 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -21,19 +22,34 @@ public WebberModule() : base(true) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override bool CanApplyEffect(Unit target) { + if (FastRandom.NextDouble() > ModifyValueByOptimalRange(target, 1.0)) + { + OnError(ErrorCodes.AccuracyCheckFailed); + + return false; + } + if (!IsCategory(CategoryFlags.cf_longrange_webber)) + { return true; + } var result = GetLineOfSight(target); + if (!result.hit) + { return true; + } OnError(ErrorCodes.LOSFailed); + return false; } @@ -45,14 +61,18 @@ protected override void OnApplyingEffect(Unit target) protected override void SetupEffect(EffectBuilder effectBuilder) { var effectProperty = _effectMassivnesSpeedMaxModifier.ToPropertyModifier(); + effectProperty.Add(effectBuilder.Owner.Massiveness); if (effectProperty.Value >= 1.0) + { effectProperty.ResetToDefaultValue(); + } - effectBuilder.SetType(EffectType.effect_demobilizer) - .SetSource(ParentRobot) - .WithPropertyModifier(effectProperty); + effectBuilder + .SetType(EffectType.effect_demobilizer) + .SetSource(ParentRobot) + .WithPropertyModifier(effectProperty); } } diff --git a/src/Perpetuum/Modules/EnergyDispersionModule.cs b/src/Perpetuum/Modules/EnergyDispersionModule.cs index 68b47471d..7f210a02f 100644 --- a/src/Perpetuum/Modules/EnergyDispersionModule.cs +++ b/src/Perpetuum/Modules/EnergyDispersionModule.cs @@ -1,5 +1,6 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.DamageProcessors; diff --git a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs index 73ff35716..2a3f85690 100644 --- a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs +++ b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs @@ -2,10 +2,11 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -22,7 +23,9 @@ public EnergyNeutralizerModule() public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void OnAction() @@ -32,6 +35,7 @@ protected override void OnAction() if (!LOSCheckAndCreateBeam(unitLock.Target)) { OnError(ErrorCodes.LOSFailed); + return; } @@ -44,14 +48,18 @@ protected override void OnAction() if ( coreNeutralized > 0.0 ) { var core = unitLock.Target.Core; + unitLock.Target.Core -= coreNeutralized; coreNeutralizedDone = Math.Abs(core - unitLock.Target.Core); - unitLock.Target.OnCombatEvent(ParentRobot, new EnergyDispersionEventArgs(coreNeutralizedDone)); - unitLock.Target.AddThreat(ParentRobot,new Threat(ThreatType.Undefined, coreNeutralizedDone / 2)); + + var threatValue = (coreNeutralizedDone / 2) + 1; + + unitLock.Target.AddThreat(ParentRobot, new Threat(ThreatType.EnWar, threatValue)); } var packet = new CombatLogPacket(CombatLogType.EnergyNeutralize, unitLock.Target, ParentRobot, this); + packet.AppendDouble(coreNeutralized); packet.AppendDouble(coreNeutralizedDone); packet.Send(unitLock.Target,ParentRobot); diff --git a/src/Perpetuum/Modules/EnergyTransfererModule.cs b/src/Perpetuum/Modules/EnergyTransfererModule.cs index 698f8a5b0..df1564673 100644 --- a/src/Perpetuum/Modules/EnergyTransfererModule.cs +++ b/src/Perpetuum/Modules/EnergyTransfererModule.cs @@ -1,10 +1,12 @@ using System; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -27,10 +29,12 @@ protected override void OnAction() if (!LOSCheckAndCreateBeam(unitLock.Target)) { OnError(ErrorCodes.LOSFailed); + return; } var coreAmount = _energyTransferAmount.Value; + coreAmount = ModifyValueByOptimalRange(unitLock.Target,coreAmount); var coreNeutralized = 0.0; @@ -39,17 +43,19 @@ protected override void OnAction() if ( coreAmount > 0.0 ) { var core = ParentRobot.Core; + ParentRobot.Core -= coreAmount; coreNeutralized = Math.Abs(core - ParentRobot.Core); var targetCore = unitLock.Target.Core; + unitLock.Target.Core += coreNeutralized; coreTransfered = Math.Abs(targetCore - unitLock.Target.Core); - unitLock.Target.SpreadAssistThreatToNpcs(ParentRobot,new Threat(ThreatType.Support, coreAmount * 2)); } var packet = new CombatLogPacket(CombatLogType.EnergyTransfer, unitLock.Target, ParentRobot, this); + packet.AppendDouble(coreAmount); packet.AppendDouble(coreNeutralized); packet.AppendDouble(coreTransfered); diff --git a/src/Perpetuum/Modules/EnergyVampireModule.cs b/src/Perpetuum/Modules/EnergyVampireModule.cs index 0519bc5e7..9875dd689 100644 --- a/src/Perpetuum/Modules/EnergyVampireModule.cs +++ b/src/Perpetuum/Modules/EnergyVampireModule.cs @@ -2,10 +2,11 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -22,7 +23,9 @@ public EnergyVampireModule() public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void OnAction() @@ -32,11 +35,13 @@ protected override void OnAction() if (!LOSCheckAndCreateBeam(unitLock.Target)) { OnError(ErrorCodes.LOSFailed); + return; } // ennyit szedunk le a targettol var coreAmount = _energyVampiredAmount.Value; + ModifyValueByReactorRadiation(unitLock.Target,ref coreAmount); // optimalrange-el modositjuk coreAmount = ModifyValueByOptimalRange(unitLock.Target,coreAmount); @@ -47,20 +52,21 @@ protected override void OnAction() if ( coreAmount > 0.0 ) { var targetCore = unitLock.Target.Core; + unitLock.Target.Core -= coreAmount; coreNeutralized = Math.Abs(targetCore - unitLock.Target.Core); - unitLock.Target.OnCombatEvent(ParentRobot,new EnergyDispersionEventArgs(coreNeutralized)); // amit sikerult leszedni azt hozzaadjuk a robothoz var core = ParentRobot.Core; + ParentRobot.Core += coreNeutralized; coreTransfered = Math.Abs(core - ParentRobot.Core); - - unitLock.Target.AddThreat(ParentRobot,new Threat(ThreatType.Undefined,coreTransfered)); + unitLock.Target.AddThreat(ParentRobot, new Threat(ThreatType.EnWar, coreTransfered + 1)); } var packet = new CombatLogPacket(CombatLogType.EnergyVampire, unitLock.Target, ParentRobot, this); + packet.AppendDouble(coreAmount); packet.AppendDouble(coreNeutralized); packet.AppendDouble(coreTransfered); diff --git a/src/Perpetuum/Modules/GangModule.cs b/src/Perpetuum/Modules/GangModule.cs index f8bac21be..4b40412da 100644 --- a/src/Perpetuum/Modules/GangModule.cs +++ b/src/Perpetuum/Modules/GangModule.cs @@ -1,6 +1,7 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; using Perpetuum.Modules.EffectModules; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Zones.Effects; namespace Perpetuum.Modules diff --git a/src/Perpetuum/Modules/GathererModule.cs b/src/Perpetuum/Modules/GathererModule.cs new file mode 100644 index 000000000..3013115d2 --- /dev/null +++ b/src/Perpetuum/Modules/GathererModule.cs @@ -0,0 +1,34 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Zones; + +namespace Perpetuum.Modules +{ + public abstract class GathererModule : ActiveModule + { + protected GathererModule(CategoryFlags ammoCategoryFlags, bool ranged = false) : base(ammoCategoryFlags, ranged) + { + coreUsage.AddEffectModifier(AggregateField.effect_core_usage_gathering_modifier); + cycleTime.AddEffectModifier(AggregateField.effect_gathering_cycle_time_modifier); + } + + protected abstract int CalculateEp(int materialType); + + protected void OnGathererMaterial(IZone zone, Player player, int materialType) + { + if (zone.Configuration.Type == ZoneType.Training) + { + return; + } + + var ep = CalculateEp(materialType); + + if (zone.Configuration.IsBeta) + { + ep *= 2; + } + + player.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Gathering, ep); + } + } +} diff --git a/src/Perpetuum/Modules/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index 29b378716..652e3616c 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -1,83 +1,43 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Transactions; -using Perpetuum.Data; +using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Zones; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains; -using Perpetuum.Zones.Terrains.Materials.Plants; using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; +using System; +using System.Diagnostics; +using System.Linq; +using System.Transactions; namespace Perpetuum.Modules { - public abstract class GathererModule : ActiveModule - { - protected GathererModule(CategoryFlags ammoCategoryFlags, bool ranged = false) : base(ammoCategoryFlags, ranged) - { - coreUsage.AddEffectModifier(AggregateField.effect_core_usage_gathering_modifier); - cycleTime.AddEffectModifier(AggregateField.effect_gathering_cycle_time_modifier); - } - - private const int MAX_EP_PER_DAY = 720; - - private int CalculateEp() - { - var activeGathererModules = ParentRobot.ActiveModules.OfType().Where(m => m.State.Type != ModuleStateType.Idle).ToArray(); - if (activeGathererModules.Length == 0) - return 0; - - var avgCycleTime = activeGathererModules.Select(m => m.CycleTime).Average(); - - var t = TimeSpan.FromDays(1).Divide(avgCycleTime); - var chance = (double) MAX_EP_PER_DAY/t.Ticks; - - chance /= activeGathererModules.Length; - - var rand = FastRandom.NextDouble(); - if (rand <= chance) - return 1; - - return 0; - } - - protected void OnGathererMaterial(IZone zone, Player player) - { - if (zone.Configuration.Type == ZoneType.Training) return; - - var ep = CalculateEp(); - - if (zone.Configuration.IsBeta) - ep *= 2; - - player.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Gathering, ep); - } - } - public class HarvesterModule : GathererModule { + private const int MAX_EP_PER_DAY = 1440; private readonly PlantHarvester.Factory _plantHarvesterFactory; private readonly HarvestingAmountModifierProperty _harverstingAmountModifier; - public HarvesterModule(CategoryFlags ammoCategoryFlags,PlantHarvester.Factory plantHarvesterFactory) : base(ammoCategoryFlags, true) + public HarvesterModule(CategoryFlags ammoCategoryFlags, PlantHarvester.Factory plantHarvesterFactory) : base(ammoCategoryFlags, true) { _plantHarvesterFactory = plantHarvesterFactory; _harverstingAmountModifier = new HarvestingAmountModifierProperty(this); AddProperty(_harverstingAmountModifier); - cycleTime.AddEffectModifier(AggregateField.effect_harvesting_cycle_time_modifier); } public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } public override void UpdateProperty(AggregateField field) @@ -90,121 +50,100 @@ public override void UpdateProperty(AggregateField field) case AggregateField.effect_harvesting_amount_modifier: { _harverstingAmountModifier.Update(); + break; } } } + protected override int CalculateEp(int materialType) + { + + HarvesterModule[] activeGathererModules = this is RemoteControlledHarvesterModule + ? ParentRobot.ActiveModules.OfType().Where(m => m.State.Type != ModuleStateType.Idle).ToArray() + : ParentRobot.ActiveModules.OfType().Where(m => m.State.Type != ModuleStateType.Idle).ToArray(); + + if (activeGathererModules.Length == 0) + { + return 0; + } + + TimeSpan avgCycleTime = activeGathererModules.Select(m => m.CycleTime).Average(); + TimeSpan t = TimeSpan.FromDays(1).Divide(avgCycleTime); + double chance = (double)MAX_EP_PER_DAY / t.Ticks; + + chance /= activeGathererModules.Length; + + double rand = FastRandom.NextDouble(); + + return rand <= chance ? 1 : 0; + } + protected override void OnAction() { - var zone = Zone; + IZone zone = Zone; + if (zone == null) + { return; + } DoHarvesting(zone); ConsumeAmmo(); } - private void DoHarvesting(IZone zone) + public void DoHarvesting(IZone zone) { - var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + TerrainLock terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + CreateBeam(terrainLock.Location, BeamState.AlignToTerrain); - using (var scope = Db.CreateTransaction()) + using (TransactionScope scope = Db.CreateTransaction()) { using (new TerrainUpdateMonitor(zone)) { - var plantInfo = zone.Terrain.Plants.GetValue(terrainLock.Location); - var amountModifier = _harverstingAmountModifier.GetValueByPlantType(plantInfo.type); + Zones.Terrains.Materials.Plants.PlantInfo plantInfo = zone.Terrain.Plants.GetValue(terrainLock.Location); + double amountModifier = _harverstingAmountModifier.GetValueByPlantType(plantInfo.type); + IPlantHarvester plantHarvester = _plantHarvesterFactory(zone, amountModifier); - var harvestedPlants = plantHarvester.HarvestPlant(terrainLock.Location); + + System.Collections.Generic.IEnumerable harvestedPlants = plantHarvester.HarvestPlant(terrainLock.Location); Debug.Assert(ParentRobot != null, "ParentRobot != null"); - var container = ParentRobot.GetContainer(); + + Robots.RobotInventory container = ParentRobot.GetContainer(); + Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); - var player = ParentRobot as Player; - Debug.Assert(player != null,"player != null"); + Player player = ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer + : ParentRobot as Player; + + Debug.Assert(player != null, "player != null"); - foreach (var extractedMaterial in harvestedPlants) + foreach (ItemInfo extractedMaterial in harvestedPlants) { - var item = (Item)Factory.CreateWithRandomEID(extractedMaterial.Definition); + Item item = (Item)Factory.CreateWithRandomEID(extractedMaterial.Definition); + item.Owner = Owner; item.Quantity = extractedMaterial.Quantity; - container.AddItem(item, true); - player.MissionHandler.EnqueueMissionEventInfo(new HarvestPlantEventInfo(player, extractedMaterial.Definition, extractedMaterial.Quantity, terrainLock.Location)); + int extractedHarvestDefinition = extractedMaterial.Definition; + int extractedQuantity = extractedMaterial.Quantity; + + player.MissionHandler.EnqueueMissionEventInfo(new HarvestPlantEventInfo(player, extractedHarvestDefinition, extractedQuantity, terrainLock.Location)); + player.Zone?.HarvestLogHandler.EnqueueHarvestLog(extractedHarvestDefinition, extractedQuantity); } - //everything went ok, save container container.Save(); - - OnGathererMaterial(zone, player); - + OnGathererMaterial(zone, player, (int)plantInfo.type); Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); } } } - - private class HarvestingAmountModifierProperty : ModuleProperty - { - public HarvestingAmountModifierProperty(HarvesterModule module) : base(module, AggregateField.harvesting_amount_modifier) - { - AddEffectModifier(AggregateField.effect_harvesting_amount_modifier); - } - - protected override double CalculateValue() - { - if (module.ParentRobot == null) - return 1.0; - - var p = module.ParentRobot.GetPropertyModifier(AggregateField.harvesting_amount_modifier); - ApplyEffectModifiers(ref p); - return p.Value; - } - - public double GetValueByPlantType(PlantType plantType) - { - var modifier = AggregateField.undefined; - - switch (plantType) - { - case PlantType.RustBush: - { - modifier = AggregateField.harvesting_amount_helioptris_modifier; - break; - } - case PlantType.SlimeRoot: - { - modifier = AggregateField.harvesting_amount_triandlus_modifier; - break; - } - case PlantType.ElectroPlant: - { - modifier = AggregateField.harvesting_amount_electroplant_modifier; - break; - } - case PlantType.TreeIron: - { - modifier = AggregateField.harvesting_amount_prismocitae_modifier; - break; - } - } - - var property = this.ToPropertyModifier(); - - if (module.ParentRobot != null && modifier != AggregateField.undefined) - { - var mod = module.ParentRobot.GetPropertyModifier(modifier); - mod.Modify(ref property); - } - - return property.Value; - } - } - } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/MiningAmmo.cs b/src/Perpetuum/Modules/MiningAmmo.cs index 56778f5d0..289ad1c5e 100644 --- a/src/Perpetuum/Modules/MiningAmmo.cs +++ b/src/Perpetuum/Modules/MiningAmmo.cs @@ -16,6 +16,7 @@ static MiningAmmo() { _modifiers = new Dictionary { + {MaterialType.FluxOre, new MiningAmmoModifier(AggregateField.mining_cycle_time_flux_modifier, AggregateField.mining_amount_flux_modifier)}, {MaterialType.Epriton, new MiningAmmoModifier(AggregateField.mining_cycle_time_epriton_modifier, AggregateField.mining_amount_epriton_modifier)}, {MaterialType.Titan, new MiningAmmoModifier(AggregateField.mining_cycle_time_titan_modifier, AggregateField.mining_amount_titan_modifier)}, {MaterialType.Stermonit, new MiningAmmoModifier(AggregateField.mining_cycle_time_stermonit_modifier, AggregateField.mining_amount_stermonit_modifier)}, diff --git a/src/Perpetuum/Modules/Module.cs b/src/Perpetuum/Modules/Module.cs index aae0d9711..bdbad69fb 100644 --- a/src/Perpetuum/Modules/Module.cs +++ b/src/Perpetuum/Modules/Module.cs @@ -1,12 +1,13 @@ -using System.Collections.Generic; -using System.Linq; -using Perpetuum.Containers; +using Perpetuum.Containers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Modules { @@ -19,11 +20,11 @@ public Module() { _powerGridUsage = new ModuleProperty(this, AggregateField.powergrid_usage); AddProperty(_powerGridUsage); - _cpuUsage = new ModuleProperty(this,AggregateField.cpu_usage); + _cpuUsage = new ModuleProperty(this, AggregateField.cpu_usage); AddProperty(_cpuUsage); } - public ILookup PropertyModifiers { get; set; } + public ILookup PropertyModifiers { get; set; } public double PowerGridUsage { @@ -63,7 +64,7 @@ public bool ParentIsPlayer() public int Slot { get { return DynamicProperties.GetOrDefault(k.slot); } - set { DynamicProperties.Update(k.slot,value); } + set { DynamicProperties.Update(k.slot, value); } } public long ModuleFlag @@ -85,7 +86,7 @@ public override Dictionary ToDictionary() var result = base.ToDictionary(); result.Add(k.slot, Slot); - result.Add(k.state, (byte) ModuleStateType.Idle); + result.Add(k.state, (byte)ModuleStateType.Idle); return result; } @@ -109,7 +110,7 @@ public Packet BuildModuleInfoPacket() var packet = new Packet(ZoneCommand.ModuleInfoResult); packet.AppendByte((byte)ParentComponent.Type); - packet.AppendByte((byte) Slot); + packet.AppendByte((byte)Slot); var properties = Properties.ToList(); @@ -139,5 +140,15 @@ public void ApplyRobotPropertyModifiers(ref ItemPropertyModifier modifier) ParentRobot?.GetPropertyModifier(m).Modify(ref modifier); } } + + public void SimulateRobotPropertyModifiers(Robot parent, ref ItemPropertyModifier modifier) + { + var modifiers = PropertyModifiers.GetOrEmpty(modifier.Field); + + foreach (var m in modifiers) + { + parent.GetPropertyModifier(m).Modify(ref modifier); + } + } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/ModuleProperties/CycleTimeProperty.cs b/src/Perpetuum/Modules/ModuleProperties/CycleTimeProperty.cs new file mode 100644 index 000000000..de4f8cdb4 --- /dev/null +++ b/src/Perpetuum/Modules/ModuleProperties/CycleTimeProperty.cs @@ -0,0 +1,42 @@ +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Modules.ModuleProperties +{ + public class CycleTimeProperty : ModuleProperty + { + private readonly ActiveModule _module; + + public CycleTimeProperty(ActiveModule module) : base(module, AggregateField.cycle_time) + { + _module = module; + } + + protected override double CalculateValue() + { + var cycleTime = _module.GetPropertyModifier(AggregateField.cycle_time); + var ammo = _module.GetAmmo(); + + ammo?.ModifyCycleTime(ref cycleTime); + + var driller = _module as DrillerModule; + + if (driller != null) + { + var miningAmmo = ammo as MiningAmmo; + + if (miningAmmo != null) + { + miningAmmo.miningCycleTimeModifier.Update(); + + var miningCycleTimeMod = miningAmmo.miningCycleTimeModifier.ToPropertyModifier(); + + miningCycleTimeMod.Modify(ref cycleTime); + } + } + + ApplyEffectModifiers(ref cycleTime); + + return cycleTime.Value; + } + } +} diff --git a/src/Perpetuum/Modules/ModuleProperties/FalloffProperty.cs b/src/Perpetuum/Modules/ModuleProperties/FalloffProperty.cs new file mode 100644 index 000000000..eee5a69be --- /dev/null +++ b/src/Perpetuum/Modules/ModuleProperties/FalloffProperty.cs @@ -0,0 +1,41 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules.Weapons; + +namespace Perpetuum.Modules.ModuleProperties +{ + public class FalloffProperty : ModuleProperty + { + public FalloffProperty(ActiveModule module) : base(module, AggregateField.falloff) + { + } + + protected override double CalculateValue() + { + var falloff = ItemPropertyModifier.Create(AggregateField.falloff); + var ammo = ((ActiveModule)module).GetAmmo(); + + if (module is MissileWeaponModule m) + { + if (ammo != null) + { + falloff = ammo.FalloffRangePropertyModifier; + + var missileRangeMod = m.MissileFalloffModifier.ToPropertyModifier(); + + missileRangeMod.Modify(ref falloff); + module.ApplyRobotPropertyModifiers(ref falloff); + } + } + else + { + falloff = module.GetPropertyModifier(AggregateField.falloff); + ammo?.ModifyFalloff(ref falloff); + } + + ApplyEffectModifiers(ref falloff); + + return falloff.Value; + } + } +} diff --git a/src/Perpetuum/Modules/ModuleProperties/HarvestingAmountModifierProperty.cs b/src/Perpetuum/Modules/ModuleProperties/HarvestingAmountModifierProperty.cs new file mode 100644 index 000000000..c48f169fb --- /dev/null +++ b/src/Perpetuum/Modules/ModuleProperties/HarvestingAmountModifierProperty.cs @@ -0,0 +1,71 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Zones.Terrains.Materials.Plants; + +namespace Perpetuum.Modules.ModuleProperties +{ + public class HarvestingAmountModifierProperty : ModuleProperty + { + public HarvestingAmountModifierProperty(GathererModule module) : base(module, AggregateField.harvesting_amount_modifier) + { + AddEffectModifier(AggregateField.effect_harvesting_amount_modifier); + } + + public double GetValueByPlantType(PlantType plantType) + { + var modifier = AggregateField.undefined; + + switch (plantType) + { + case PlantType.RustBush: + { + modifier = AggregateField.harvesting_amount_helioptris_modifier; + + break; + } + case PlantType.SlimeRoot: + { + modifier = AggregateField.harvesting_amount_triandlus_modifier; + + break; + } + case PlantType.ElectroPlant: + { + modifier = AggregateField.harvesting_amount_electroplant_modifier; + + break; + } + case PlantType.TreeIron: + { + modifier = AggregateField.harvesting_amount_prismocitae_modifier; + + break; + } + } + + var property = this.ToPropertyModifier(); + + if (module.ParentRobot != null && modifier != AggregateField.undefined) + { + var mod = module.ParentRobot.GetPropertyModifier(modifier); + + mod.Modify(ref property); + } + + return property.Value; + } + + protected override double CalculateValue() + { + if (module.ParentRobot == null) + { + return 1.0; + } + + var p = module.ParentRobot.GetPropertyModifier(AggregateField.harvesting_amount_modifier); + + ApplyEffectModifiers(ref p); + + return p.Value; + } + } +} diff --git a/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs b/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs new file mode 100644 index 000000000..b96261939 --- /dev/null +++ b/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs @@ -0,0 +1,31 @@ +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Modules.ModuleProperties +{ + public class MiningAmountModifierProperty : ModuleProperty + { + private readonly GathererModule _module; + + public MiningAmountModifierProperty(GathererModule module) : base(module, AggregateField.mining_amount_modifier) + { + _module = module; + AddEffectModifier(AggregateField.effect_mining_amount_modifier); + } + + protected override double CalculateValue() + { + if (module.ParentRobot == null) + { + return 1.0; + } + + var m = module.ParentRobot.GetPropertyModifier(AggregateField.mining_amount_modifier); + var ammo = (MiningAmmo)_module.GetAmmo(); + + ammo?.ApplyMiningAmountModifier(ref m); + module.ParentRobot?.ApplyEffectPropertyModifiers(AggregateField.effect_mining_amount_modifier, ref m); + + return m.Value; + } + } +} diff --git a/src/Perpetuum/Modules/ModuleProperties/ModuleProperty.cs b/src/Perpetuum/Modules/ModuleProperties/ModuleProperty.cs new file mode 100644 index 000000000..82d27dd1e --- /dev/null +++ b/src/Perpetuum/Modules/ModuleProperties/ModuleProperty.cs @@ -0,0 +1,59 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using System.Collections.Generic; + +namespace Perpetuum.Modules.ModuleProperties +{ + public class ModuleProperty : ItemProperty + { + protected readonly Module module; + private List _effectModifiers; + + public ModuleProperty(Module module, AggregateField field) : base(field) + { + this.module = module; + } + + public void AddEffectModifier(AggregateField field) + { + if (_effectModifiers == null) + { + _effectModifiers = new List(); + } + + _effectModifiers.Add(field); + } + + protected override double CalculateValue() + { + var m = module.GetPropertyModifier(Field); + + ApplyEffectModifiers(ref m); + + return m.Value; + } + + protected void ApplyEffectModifiers(ref ItemPropertyModifier m) + { + if (_effectModifiers == null) + { + return; + } + + foreach (var effectModifier in _effectModifiers) + { + module.ParentRobot?.ApplyEffectPropertyModifiers(effectModifier, ref m); + } + } + + protected override bool IsRelated(AggregateField field) + { + if (_effectModifiers != null && _effectModifiers.Contains(field)) + { + return true; + } + + return base.IsRelated(field); + } + } +} diff --git a/src/Perpetuum/Modules/ModuleProperties/OptimalRangeProperty.cs b/src/Perpetuum/Modules/ModuleProperties/OptimalRangeProperty.cs new file mode 100644 index 000000000..db42bd69f --- /dev/null +++ b/src/Perpetuum/Modules/ModuleProperties/OptimalRangeProperty.cs @@ -0,0 +1,45 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules.Weapons; + +namespace Perpetuum.Modules.ModuleProperties +{ + public class OptimalRangeProperty : ModuleProperty + { + private readonly ActiveModule _module; + + public OptimalRangeProperty(ActiveModule module) : base(module, AggregateField.optimal_range) + { + _module = module; + AddEffectModifier(AggregateField.effect_optimal_range_modifier); + } + + protected override double CalculateValue() + { + var optimalRange = ItemPropertyModifier.Create(AggregateField.optimal_range); + var ammo = _module.GetAmmo(); + + if (module is MissileWeaponModule m) + { + if (ammo != null) + { + optimalRange = ammo.OptimalRangePropertyModifier; + + var missileRangeMod = m.MissileRangeModifier.ToPropertyModifier(); + + missileRangeMod.Modify(ref optimalRange); + module.ApplyRobotPropertyModifiers(ref optimalRange); + } + } + else + { + optimalRange = module.GetPropertyModifier(AggregateField.optimal_range); + ammo?.ModifyOptimalRange(ref optimalRange); + } + + ApplyEffectModifiers(ref optimalRange); + + return optimalRange.Value; + } + } +} diff --git a/src/Perpetuum/Modules/RemoteArmorRepairModule.cs b/src/Perpetuum/Modules/RemoteArmorRepairModule.cs index 74177f3eb..660f87d2a 100644 --- a/src/Perpetuum/Modules/RemoteArmorRepairModule.cs +++ b/src/Perpetuum/Modules/RemoteArmorRepairModule.cs @@ -1,6 +1,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using Perpetuum.Zones.PBS; namespace Perpetuum.Modules @@ -16,19 +17,19 @@ protected override void OnAction() var unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); (ParentIsPlayer() && unitLock.Target is Npc).ThrowIfTrue(ErrorCodes.ThisModuleIsNotSupportedOnNPCs); - // csak pbs-re nezzuk ((unitLock.Target is IPBSObject) && unitLock.Target.Armor.Ratio(unitLock.Target.ArmorMax) >= 1.0).ThrowIfTrue(ErrorCodes.BuildingIsAtFullHealth); if (!LOSCheckAndCreateBeam(unitLock.Target)) { OnError(ErrorCodes.LOSFailed); + return; } var repairAmount = armorRepairAmount.Value; - repairAmount = ModifyValueByOptimalRange(unitLock.Target,repairAmount); + repairAmount = ModifyValueByOptimalRange(unitLock.Target,repairAmount); OnRepair(unitLock.Target,repairAmount); unitLock.Target.SpreadAssistThreatToNpcs(ParentRobot,new Threat(ThreatType.Support,repairAmount * 2)); } diff --git a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs new file mode 100644 index 000000000..263e18751 --- /dev/null +++ b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs @@ -0,0 +1,31 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Terrains.Materials; + +namespace Perpetuum.Modules +{ + public class RemoteControlledDrillerModule : DrillerModule + { + public RemoteControlledDrillerModule(RareMaterialHandler rareMaterialHandler, MaterialHelper materialHelper) + : base(CategoryFlags.undefined, rareMaterialHandler, materialHelper) + { + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + protected override void OnAction() + { + if (Zone != null) + { + DoExtractMinerals(Zone); + } + } + } +} diff --git a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs new file mode 100644 index 000000000..fb72ff92a --- /dev/null +++ b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs @@ -0,0 +1,30 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; + +namespace Perpetuum.Modules +{ + public class RemoteControlledHarvesterModule : HarvesterModule + { + public RemoteControlledHarvesterModule(PlantHarvester.Factory plantHarvesterFactory) : base(CategoryFlags.undefined, plantHarvesterFactory) + { + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + protected override void OnAction() + { + if (Zone != null) + { + DoHarvesting(Zone); + } + } + } +} diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs new file mode 100644 index 000000000..157b9c257 --- /dev/null +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -0,0 +1,226 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Players; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using Perpetuum.Zones; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.Finders; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Teleporting; +using System; +using System.Linq; + +namespace Perpetuum.Modules +{ + public class RemoteControllerModule : ActiveModule + { + private const int SentryTurretHeight = 7; + private const double SpawnRangeMin = 2; + private const double SpawnRangeMax = 5; + private readonly ModuleProperty bandwidthMax; + private readonly ModuleProperty operationalRange; + private readonly ModuleProperty lifetime; + private BandwidthHandler bandwidthHandler; + + public double BandwidthMax => bandwidthMax.Value; + + public RemoteControllerModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) + { + optimalRange.AddEffectModifier(AggregateField.effect_ew_optimal_range_modifier); + + bandwidthMax = new ModuleProperty(this, AggregateField.remote_control_bandwidth_max); + AddProperty(bandwidthMax); + + operationalRange = new ModuleProperty(this, AggregateField.remote_control_operational_range); + AddProperty(operationalRange); + + lifetime = new ModuleProperty(this, AggregateField.remote_control_lifetime); + AddProperty(lifetime); + + InitBandwidthHandler(this); + } + + private void InitBandwidthHandler(RemoteControllerModule module) + { + bandwidthHandler = new BandwidthHandler(module); + } + + public void SyncRemoteChannels() + { + bandwidthHandler.Update(); + } + + public bool HasFreeBandwidthFor(RemoteControlledUnit unit) + { + return bandwidthHandler.HasFreeBandwidthFor(unit); + } + + public void UseRemoteChannel(RemoteControlledCreature turret) + { + bandwidthHandler.UseRemoteChannel(turret); + turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; + bandwidthHandler.Update(); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + public void CloseAllChannels() + { + bandwidthHandler.CloseAllChannels(); + } + + protected override void OnAction() + { + if (bandwidthHandler == null) + { + return; + } + + SyncRemoteChannels(); + + RemoteControlledUnit ammo = GetAmmo() as RemoteControlledUnit; + + HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); + + Position targetPosition; + + if (ammo.ED.Options.TurretType != TurretType.CombatDrone) + { + + Lock myLock = GetLock(); + + if (myLock is TerrainLock) + { + targetPosition = (myLock as TerrainLock).Location; + } + else if (myLock is UnitLock) + { + targetPosition = (myLock as UnitLock).Target.CurrentPosition; + } + else + { + OnError(ErrorCodes.InvalidLockType); + + return; + } + + Zone.Units + .OfType() + .WithinRange(Zone.FixZ(targetPosition), DistanceConstants.RCU_DEPLOY_RANGE_FROM_RCU) + .Any() + .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); + + LOSResult r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); + + if (r.hit) + { + OnError(ErrorCodes.LOSFailed); + + return; + } + } + else + { + targetPosition = GetSpawnPosition(ParentRobot.CurrentPosition); + } + + Player player = ParentRobot is Player robotAsPlayer + ? robotAsPlayer + : null; + + + if (player != null) + { + ammo.CheckEnablerExtensionsAndThrowIfFailed(player.Character, ErrorCodes.ExtensionLevelMismatchTerrain); + } + + RemoteControlledCreature remoteControlledCreature; + if (ammo.ED.Options.TurretType == TurretType.Sentry) + { + remoteControlledCreature = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + remoteControlledCreature.Behavior = Behavior.Create(BehaviorType.RemoteControlledTurret); + + Position spawnPosition = Zone.FixZ(targetPosition); + + Zone.Units + .OfType() + .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_BASE) + .Any() + .ThrowIfTrue(ErrorCodes.TurretNotDeployableNearObject); + + Zone.Units + .OfType() + .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_TELEPORT) + .Any() + .ThrowIfTrue(ErrorCodes.TurretNotDeployableNearObject); + } + else if (ammo.ED.Options.TurretType == TurretType.Mining || ammo.ED.Options.TurretType == TurretType.Harvesting) + { + remoteControlledCreature = (IndustrialTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + (remoteControlledCreature as IndustrialTurret).SetTurretType(ammo.ED.Options.TurretType); + remoteControlledCreature.Behavior = Behavior.Create(BehaviorType.RemoteControlledTurret); + } + else if (ammo.ED.Options.TurretType == TurretType.CombatDrone) + { + remoteControlledCreature = (CombatDrone)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + remoteControlledCreature.Behavior = Behavior.Create(BehaviorType.RemoteControlledDrone); + (remoteControlledCreature as CombatDrone).GuardRange = 5; + } + else + { + _ = PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); + + return; + } + + remoteControlledCreature.SetCommandRobot(player ?? ParentRobot); + + remoteControlledCreature.Owner = ParentRobot.Owner; + remoteControlledCreature.SetBandwidthUsage(ammo.RemoteChannelBandwidthUsage); + + UseRemoteChannel(remoteControlledCreature); + + remoteControlledCreature.DespawnTime = TimeSpan.FromMilliseconds(lifetime.Value); + remoteControlledCreature.SetGroup(bandwidthHandler); + + ClosestWalkablePositionFinder finder = new ClosestWalkablePositionFinder(Zone, targetPosition); + Position position = finder.FindOrThrow(); + + remoteControlledCreature.HomePosition = position; + remoteControlledCreature.HomeRange = operationalRange.Value; + remoteControlledCreature.Orientation = FastRandom.NextInt(0, 3) * 0.25; + remoteControlledCreature.CallForHelp = true; + + BeamBuilder deployBeamBuilder = Beam.NewBuilder() + .WithType(BeamType.dock_in) + .WithSource(remoteControlledCreature.CommandRobot) + .WithTarget(remoteControlledCreature) + .WithState(BeamState.Hit) + .WithDuration(TimeSpan.FromSeconds(5)); + + remoteControlledCreature.AddToZone(Zone, position, ZoneEnterType.Default, deployBeamBuilder); + ConsumeAmmo(); + } + + private Position GetSpawnPosition(Position spawnOrigin) + { + double spawnRangeMin = SpawnRangeMin; + double spawnRangeMax = SpawnRangeMax; + Position spawnPosition = spawnOrigin.GetRandomPositionInRange2D(spawnRangeMin, spawnRangeMax).Clamp(Zone.Size); + + return spawnPosition; + } + } +} diff --git a/src/Perpetuum/Modules/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index abbc4c1db..3b0c2c15c 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -1,11 +1,12 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Robots; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -23,30 +24,30 @@ public SensorJammerModule() : base(true) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } - private const double THREAT_SENSOR_JAMMER = 75.0; - protected override void OnAction() { var unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); var robot = unitLock.Target.ThrowIfNotType(ErrorCodes.InvalidTarget); - + var success = false; var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble(); - targetSensorStrength = ModifyValueByOptimalRange(robot, targetSensorStrength); + var rangedEcmStrength = ModifyValueByOptimalRange(robot, _ecmStrength.Value); - var success = targetSensorStrength < _ecmStrength.Value; - if (success) + if (targetSensorStrength < rangedEcmStrength) { robot.ResetLocks(); - robot.AddThreat(ParentRobot, new Threat(ThreatType.Undefined, THREAT_SENSOR_JAMMER)); + robot.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); + success = true; } var packet = new CombatLogPacket(CombatLogType.Jammer, robot,ParentRobot,this); + packet.AppendByte(success.ToByte()); packet.Send(ParentRobot,robot); - robot.OnCombatEvent(ParentRobot,new SensorJammerEventArgs(success)); } } diff --git a/src/Perpetuum/Modules/Weapons/Damage.cs b/src/Perpetuum/Modules/Weapons/Damage.cs index c13c8d621..c98d1d85a 100644 --- a/src/Perpetuum/Modules/Weapons/Damage.cs +++ b/src/Perpetuum/Modules/Weapons/Damage.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Perpetuum.Builders; using Perpetuum.Units; @@ -29,7 +30,7 @@ public interface IDamageBuilder : IBuilder public static class DamageBuilderExtensions { - public static IDamageBuilder WithAllDamageTypes(this IDamageBuilder builder,double damage) + public static IDamageBuilder WithAllDamageTypes(this IDamageBuilder builder, double damage) { return builder.WithDamage(DamageType.Chemical, damage) .WithDamage(DamageType.Explosive, damage) @@ -37,7 +38,7 @@ public static IDamageBuilder WithAllDamageTypes(this IDamageBuilder builder,doub .WithDamage(DamageType.Thermal, damage); } - public static IDamageBuilder WithDamages(this IDamageBuilder builder,IEnumerable damages) + public static IDamageBuilder WithDamages(this IDamageBuilder builder, IEnumerable damages) { foreach (var damage in damages) builder.WithDamage(damage); @@ -45,7 +46,7 @@ public static IDamageBuilder WithDamages(this IDamageBuilder builder,IEnumerable return builder; } - public static IDamageBuilder WithDamage(this IDamageBuilder builder,DamageType type, double damage) + public static IDamageBuilder WithDamage(this IDamageBuilder builder, DamageType type, double damage) { Debug.Assert(!double.IsNaN(damage)); return Math.Abs(damage - 0.0) < double.Epsilon ? builder : builder.WithDamage(new Damage(type, damage)); @@ -60,6 +61,7 @@ public sealed class DamageInfo public Position sourcePosition; public bool IsCritical { get; private set; } public IList damages = new List(); + public IList plantDamages = new List(); private double _optimalRange; private double _falloff; private double _explosionRadius; @@ -122,20 +124,20 @@ public IList CalculateDamages(Unit target) damageModifier *= tmpDamageMod; } - foreach (var cleanDamage in damages) - { - var damageValue = cleanDamage.value * damageModifier; - - Debug.Assert(!double.IsNaN(damageValue)); - - result.Add(new Damage(cleanDamage.type, damageValue)); - } + result = damages.Where(d => d.type != DamageType.Toxic) + .Select(d => new Damage(d.type, d.value * damageModifier)) + .ToList(); } } return result; } + public double CalculatePlantDamages() + { + return damages.Sum(d => d.value) + plantDamages.Sum(d => d.value); + } + public static IDamageBuilder Builder { get { return new DamageBuilder(); } @@ -146,6 +148,7 @@ private class DamageBuilder : IDamageBuilder private Unit _attacker; private Position _sourcePosition; private readonly IList _damages = new List(); + private readonly IList _plantDamages = new List(); private double _optimalRange; private double _falloff; private double _explosionRadius; @@ -188,7 +191,14 @@ public IDamageBuilder WithExplosionRadius(double explosionRadius) public IDamageBuilder WithDamage(Damage damage) { - _damages.Add(damage); + if (damage.type == DamageType.Toxic) + { + _plantDamages.Add(damage); + } + else + { + _damages.Add(damage); + } return this; } @@ -199,6 +209,7 @@ public DamageInfo Build() attacker = _attacker, sourcePosition = _sourcePosition, damages = _damages, + plantDamages = _plantDamages, _optimalRange = _optimalRange, _falloff = _falloff, _explosionRadius = _explosionRadius diff --git a/src/Perpetuum/Modules/Weapons/DamageType.cs b/src/Perpetuum/Modules/Weapons/DamageType.cs index 562b7f303..79fe99bfe 100644 --- a/src/Perpetuum/Modules/Weapons/DamageType.cs +++ b/src/Perpetuum/Modules/Weapons/DamageType.cs @@ -5,6 +5,7 @@ public enum DamageType Chemical, Kinetic, Thermal, - Explosive + Explosive, + Toxic } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs new file mode 100644 index 000000000..04ae4b9b3 --- /dev/null +++ b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs @@ -0,0 +1,32 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Units; + +namespace Perpetuum.Modules.Weapons +{ + /// + /// Special subclass of weapon with special capabilities against plants + /// applies damage like missiles and doesn't miss. + /// + public class FirearmWeaponModule : WeaponModule + { + public ModuleProperty PlantDamageModifier { get; } + + public FirearmWeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags) + { + PlantDamageModifier = new ModuleProperty(this, AggregateField.damage_toxic_modifier); + AddProperty(PlantDamageModifier); + } + + protected override bool CheckAccuracy(Unit victim) + { + return false; + } + + protected override IDamageBuilder GetDamageBuilder() + { + return base.GetDamageBuilder() + .WithExplosionRadius(Accuracy.Value); + } + } +} diff --git a/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs b/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs index a352c4edd..3c20334d4 100644 --- a/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs @@ -1,6 +1,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; namespace Perpetuum.Modules.Weapons @@ -9,6 +10,7 @@ public class MissileWeaponModule : WeaponModule { private readonly ItemProperty _propertyExplosionRadius; public readonly ModuleProperty MissileRangeModifier; + public readonly ModuleProperty MissileFalloffModifier; public MissileWeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags) { @@ -17,6 +19,8 @@ public MissileWeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryF MissileRangeModifier = new ModuleProperty(this, AggregateField.module_missile_range_modifier); MissileRangeModifier.AddEffectModifier(AggregateField.effect_missile_range_modifier); AddProperty(MissileRangeModifier); + MissileFalloffModifier = new ModuleProperty(this, AggregateField.module_missile_falloff_modifier); + AddProperty(MissileFalloffModifier); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -41,6 +45,11 @@ public override void UpdateProperty(AggregateField field) MissileRangeModifier.Update(); return; } + case AggregateField.module_missile_falloff_modifier: + { + MissileFalloffModifier.Update(); + return; + } } base.UpdateProperty(field); diff --git a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs index 7d3a3b78a..cdfd2ba87 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs @@ -3,6 +3,7 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; using Perpetuum.Items.Ammos; +using Perpetuum.Zones.RemoteControl; namespace Perpetuum.Modules.Weapons { @@ -30,17 +31,29 @@ public override void UpdateAllProperties() public IList GetCleanDamages() { - return LazyInitializer.EnsureInitialized(ref _cleanDamages,CalculateCleanDamages); + return LazyInitializer.EnsureInitialized(ref _cleanDamages, CalculateCleanDamages); } private IList CalculateCleanDamages() { var result = new List(); - var weapon = this.GetParentModule() as WeaponModule; - if (weapon == null) + if (!(GetParentModule() is WeaponModule weapon)) return result; + if (weapon is FirearmWeaponModule firearm) + { + var plantDmgMod = firearm.PlantDamageModifier.ToPropertyModifier(); + + var plantDmgProperty = GetPropertyModifier(AggregateField.damage_toxic); + + if (plantDmgProperty.HasValue) + { + plantDmgMod.Modify(ref plantDmgProperty); + result.Add(new Damage(DamageType.Toxic, plantDmgProperty.Value)); + } + } + var damageModifier = weapon.DamageModifier.ToPropertyModifier(); var property = GetPropertyModifier(AggregateField.damage_chemical); @@ -69,8 +82,10 @@ private IList CalculateCleanDamages() property = GetPropertyModifier(AggregateField.damage_explosive); - if (!property.HasValue) + if (!property.HasValue) + { return result; + } damageModifier.Modify(ref property); result.Add(new Damage(DamageType.Explosive, property.Value)); diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 2c4d80c14..0b4a31243 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Perpetuum.EntityFramework; +using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Perpetuum.Modules.Weapons { @@ -17,21 +17,17 @@ public class WeaponModule : ActiveModule { private readonly ModuleAction _action; - private readonly ModuleProperty _damageModifier; - private readonly ModuleProperty _accuracy; + public ModuleProperty DamageModifier { get; } - public ModuleProperty DamageModifier - { - get { return _damageModifier; } - } + public ModuleProperty Accuracy { get; } public WeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) { _action = new ModuleAction(this); - _damageModifier = new ModuleProperty(this,AggregateField.damage_modifier); - AddProperty(_damageModifier); - _accuracy = new ModuleProperty(this, AggregateField.accuracy); - AddProperty(_accuracy); + DamageModifier = new ModuleProperty(this, AggregateField.damage_modifier); + AddProperty(DamageModifier); + Accuracy = new ModuleProperty(this, AggregateField.accuracy); + AddProperty(Accuracy); cycleTime.AddEffectModifier(AggregateField.effect_weapon_cycle_time_modifier); } @@ -39,7 +35,38 @@ public WeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, t public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } + } + + protected override void OnAction() + { + _action.DoAction(); + } + + protected virtual bool CheckAccuracy(Unit victim) + { + double rnd = FastRandom.NextDouble(); + bool isMiss = rnd * Accuracy.Value > victim.SignatureRadius; + + return isMiss; + } + + protected virtual IDamageBuilder GetDamageBuilder() + { + return DamageInfo.Builder + .WithAttacker(ParentRobot) + .WithOptimalRange(OptimalRange) + .WithFalloff(Falloff) + .WithDamages(GetCleanDamages()); + } + + private IEnumerable GetCleanDamages() + { + WeaponAmmo ammo = (WeaponAmmo)GetAmmo(); + + return ammo != null ? ammo.GetCleanDamages() : new Damage[0]; } private class ModuleAction : ILockVisitor @@ -55,7 +82,8 @@ public void DoAction() { _weapon.ParentRobot.HasShieldEffect.ThrowIfTrue(ErrorCodes.ShieldIsActive); - var currentLock = _weapon.GetLock(); + Lock currentLock = _weapon.GetLock(); + currentLock?.AcceptVisitor(this); } @@ -66,57 +94,66 @@ public void VisitLock(Lock @lock) public void VisitUnitLock(UnitLock unitLock) { - var victim = unitLock.Target; + Unit victim = unitLock.Target; victim.InZone.ThrowIfFalse(ErrorCodes.TargetNotFound); victim.States.Dead.ThrowIfTrue(ErrorCodes.TargetIsDead); - var err = victim.IsAttackable; + ErrorCodes err = victim.IsAttackable; + if (err != ErrorCodes.NoError) + { throw new PerpetuumException(err); + } victim.IsInvulnerable.ThrowIfTrue(ErrorCodes.TargetIsInvulnerable); - _weapon.ConsumeAmmo(); - var result = _weapon.GetLineOfSight(victim); + LOSResult result = _weapon.GetLineOfSight(victim); + if (result.hit) { DoDamageToPosition(result.position); _weapon.OnError(ErrorCodes.LOSFailed); + return; } - var distance = _weapon.ParentRobot.GetDistance(victim); - var bulletTime = _weapon.GetAmmo().BulletTime; - var flyTime = (int)((distance / bulletTime) * 1000); - var beamTime = (int)Math.Max(flyTime, _weapon.CycleTime.TotalMilliseconds); - - var miss = _weapon.CheckAccuracy(victim); + double distance = _weapon.ParentRobot.GetDistance(victim); + double bulletTime = _weapon.GetAmmo().BulletTime; + int flyTime = (int)(distance / bulletTime * 1000); + int beamTime = (int)Math.Max(flyTime, _weapon.CycleTime.TotalMilliseconds); + bool miss = _weapon.CheckAccuracy(victim); + if (miss) { - _weapon.CreateBeam(victim, BeamState.Miss,beamTime,bulletTime); + _ = _weapon.CreateBeam(victim, BeamState.Miss, beamTime, bulletTime); _weapon.OnError(ErrorCodes.AccuracyCheckFailed); + return; } - var delay = _weapon.CreateBeam(victim, BeamState.Hit, beamTime, bulletTime); + int delay = _weapon.CreateBeam(victim, BeamState.Hit, beamTime, bulletTime); + flyTime += delay; - var builder = _weapon.GetDamageBuilder(); - Task.Delay(flyTime).ContinueWith(t => victim.TakeDamage(builder.Build())); + IDamageBuilder builder = _weapon.GetDamageBuilder(); + + _ = Task.Delay(flyTime).ContinueWith(t => victim.TakeDamage(builder.Build())); } public void VisitTerrainLock(TerrainLock terrainLock) { - var location = terrainLock.Location; + Position location = terrainLock.Location; _weapon.ConsumeAmmo(); - var blockingInfo = _weapon?.ParentRobot?.Zone?.Terrain.Blocks.GetValue(terrainLock.Location) ?? BlockingInfo.None; - location = location.AddToZ(Math.Min(blockingInfo.Height,20)); + BlockingInfo blockingInfo = _weapon?.ParentRobot?.Zone?.Terrain.Blocks.GetValue(terrainLock.Location) ?? BlockingInfo.None; + + location = location.AddToZ(Math.Min(blockingInfo.Height, 20)); + + LOSResult losResult = _weapon.GetLineOfSight(location); - var losResult = _weapon.GetLineOfSight(location); if (losResult.hit && !location.IsEqual2D(losResult.position)) { location = losResult.position; @@ -128,57 +165,37 @@ public void VisitTerrainLock(TerrainLock terrainLock) private void DoDamageToPosition(Position location) { - var distance = _weapon.ParentRobot.CurrentPosition.TotalDistance3D(location); - var bulletTime = _weapon.GetAmmo().BulletTime; - var flyTime = (int) ((distance/bulletTime)*1000); + double distance = _weapon.ParentRobot.CurrentPosition.TotalDistance3D(location); + double bulletTime = _weapon.GetAmmo().BulletTime; + int flyTime = (int)(distance / bulletTime * 1000); + int beamTime = (int)Math.Max(flyTime, _weapon.CycleTime.TotalMilliseconds); - var beamTime = (int) Math.Max(flyTime, _weapon.CycleTime.TotalMilliseconds); flyTime += _weapon.CreateBeam(location, BeamState.Hit, beamTime, bulletTime); - var damage = _weapon.GetCleanDamages().Sum(d => d.value); + double damage = _weapon.GetDamageBuilder().Build().CalculatePlantDamages(); + if (damage <= 0.0) + { return; + } + + IZone zone = _weapon.Zone; - var zone = _weapon.Zone; if (zone == null) + { return; + } - Task.Delay(flyTime).ContinueWith(t => DealDamageToPosition(zone,location,damage)); + _ = Task.Delay(flyTime).ContinueWith(t => DealDamageToPosition(zone, location, damage)); } - private static void DealDamageToPosition(IZone zone,Position location,double damage) + private static void DealDamageToPosition(IZone zone, Position location, double damage) { using (new TerrainUpdateMonitor(zone)) { - zone.DamageToPlantOnArea(Area.FromRadius(location,1),damage/2); + zone.DamageToPlantOnArea(Area.FromRadius(location, 1), damage / 2.0); } } } - - protected override void OnAction() - { - _action.DoAction(); - } - - protected virtual bool CheckAccuracy(Unit victim) - { - var rnd = FastRandom.NextDouble(); - var isMiss = rnd * _accuracy.Value > victim.SignatureRadius; - return isMiss; - } - - protected virtual IDamageBuilder GetDamageBuilder() - { - return DamageInfo.Builder.WithAttacker(ParentRobot) - .WithOptimalRange(OptimalRange) - .WithFalloff(Falloff) - .WithDamages(GetCleanDamages()); - } - - private IEnumerable GetCleanDamages() - { - var ammo = (WeaponAmmo)GetAmmo(); - return ammo != null ? ammo.GetCleanDamages() : new Damage[0]; - } } } \ No newline at end of file diff --git a/src/Perpetuum/Network/Http.cs b/src/Perpetuum/Network/Http.cs index dc894e756..ceb6f79a3 100644 --- a/src/Perpetuum/Network/Http.cs +++ b/src/Perpetuum/Network/Http.cs @@ -13,9 +13,10 @@ public static string Post(string address, IEnumerable + /// An AStar PathFinder with cheaper path-existance check with max path-depth + /// + public class AStarLimited : AStarFinder + { + private readonly int MAX_PQ_SIZE; + private readonly int MAX_DEPTH; + public AStarLimited(Heuristic heuristic, PathFinderNodePassableHandler passableHandler, int max) : base(heuristic, passableHandler) + { + MAX_PQ_SIZE = max * max + 1; + MAX_DEPTH = max; + } + + /// + /// Checks only for if a path exists between two points and is less than the MAX_DEPTH + /// + /// Start point + /// End point + /// True if path is found and shorter than MAX_DEPTH + public bool HasPath(Point start, Point end) + { + if (!_passableHandler(end.X, end.Y)) + return false; + + if (start == end) + return true; + + var openList = new PriorityQueue(MAX_PQ_SIZE); + var visited = new HashSet(); + + var startNode = new Node(start.X, start.Y); + openList.Enqueue(startNode); + visited.Add(startNode.GetHashCode()); + + while (openList.TryDequeue(out Node node)) + { + if (node.Location == end) + return true; + + if (node.depth > MAX_DEPTH) + return false; + + foreach (var neighbor in GetNeighbors(node)) + { + var neighborHash = neighbor.GetHashCode(); + if (visited.Contains(neighborHash)) + continue; + + if (neighbor.Location == end) + return true; + + var newG = node.g + (int)(Weight * (neighbor.Location.X - node.Location.X == 0 || neighbor.Location.Y - node.Location.Y == 0 ? 1 : SQRT2)); + var newH = _heuristic.Calculate(neighbor.Location.X, neighbor.Location.Y, end.X, end.Y) * Weight; + + neighbor.g = newG; + neighbor.f = newG + newH; + neighbor.parent = node; + neighbor.depth = node.depth + 1; + + visited.Add(neighborHash); + openList.Enqueue(neighbor); + } + } + return false; + } + } + + + public class AStarFinder : PathFinder { - private readonly Heuristic _heuristic; - private readonly PathFinderNodePassableHandler _passableHandler; + protected readonly Heuristic _heuristic; + protected readonly PathFinderNodePassableHandler _passableHandler; public AStarFinder(Heuristic heuristic,PathFinderNodePassableHandler passableHandler) { @@ -67,7 +138,7 @@ public override Point[] FindPath(Point start, Point end,CancellationToken cancel return null; } - private Point[] Backtrace(Node node) + protected Point[] Backtrace(Node node) { var stack = new Stack(); @@ -81,9 +152,9 @@ private Point[] Backtrace(Node node) return stack.ToArray(); } - private static readonly sbyte[,] _n = { { -1, -1 }, { 0, -1 }, { 1, -1 }, { -1, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 } }; + protected static readonly sbyte[,] _n = { { -1, -1 }, { 0, -1 }, { 1, -1 }, { -1, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 } }; - private IEnumerable GetNeighbors(Node node) + protected IEnumerable GetNeighbors(Node node) { for (var i = 0; i < 8; i++) { @@ -97,11 +168,12 @@ private IEnumerable GetNeighbors(Node node) } } - private class Node : PathFinderNode,IComparable + protected class Node : PathFinderNode, IComparable { public int g; public int f; public Node parent; + public int depth = 0; public Node(int x, int y) : base(x, y) { diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index b5178502b..8192ad755 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -70,12 +70,14 @@ x64 + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + ..\..\packages\System.Collections.Immutable.1.4.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - 3.5 @@ -90,7 +92,6 @@ - 3.0 @@ -152,6 +153,7 @@ + @@ -165,6 +167,7 @@ + @@ -214,6 +217,16 @@ + + + + + + + + + + @@ -246,7 +259,19 @@ + + + + + + + + + + + + @@ -261,6 +286,9 @@ + + + @@ -273,16 +301,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -319,7 +400,7 @@ - + @@ -505,6 +586,15 @@ + + + + + + + + + @@ -529,6 +619,7 @@ + @@ -552,6 +643,7 @@ + @@ -570,6 +662,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -620,36 +789,62 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + @@ -757,6 +952,7 @@ + @@ -841,6 +1037,12 @@ + + + + + + @@ -916,7 +1118,6 @@ - @@ -964,7 +1165,7 @@ - + @@ -1083,8 +1284,6 @@ - - @@ -1100,11 +1299,14 @@ + + + @@ -1147,6 +1349,7 @@ + diff --git a/src/Perpetuum/Players/ErrorPacketBuilder.cs b/src/Perpetuum/Players/ErrorPacketBuilder.cs new file mode 100644 index 000000000..cd276914a --- /dev/null +++ b/src/Perpetuum/Players/ErrorPacketBuilder.cs @@ -0,0 +1,20 @@ +using Perpetuum.Builders; +using Perpetuum.Zones; + +namespace Perpetuum.Players +{ + public class ErrorPacketBuilder : IBuilder + { + private readonly ErrorCodes error; + + public ErrorPacketBuilder(ErrorCodes error) + { + this.error = error; + } + + public Packet Build() + { + return new Packet(ZoneCommand.Error) { Error = error }; + } + } +} diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 979896ad2..a224a1c98 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Transactions; -using Perpetuum.Accounting.Characters; +using Perpetuum.Accounting.Characters; using Perpetuum.Builders; using Perpetuum.Collections.Spatial; using Perpetuum.Common.Loggers.Transaction; @@ -43,136 +36,53 @@ using Perpetuum.Zones.PBS; using Perpetuum.Zones.PlantTools; using Perpetuum.Zones.ProximityProbes; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Teleporting; using Perpetuum.Zones.Teleporting.Strategies; using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; namespace Perpetuum.Players { - public class PlayerMovement - { - private static readonly TimeSpan _minStepTime = TimeSpan.FromMilliseconds(50); - private static readonly TimeSpan _maxElapsedTime = TimeSpan.FromSeconds(1); - private readonly Player _player; - - public PlayerMovement(Player player) - { - _player = player; - } - - public void Update(TimeSpan elapsed) - { - var speed = _player.Speed; - if (speed <= 0.0) - return; - - elapsed = elapsed.Min(_maxElapsedTime); - - var angle = _player.Direction * Math.PI * 2; - var vx = Math.Sin(angle) * speed; - var vy = Math.Cos(angle) * speed; - - var px = _player.CurrentPosition.X; - var py = _player.CurrentPosition.Y; - - while (elapsed > TimeSpan.Zero) - { - var time = _minStepTime; - - if (elapsed < _minStepTime) - time = elapsed; - - elapsed -= _minStepTime; - - var nx = px + (vx * time.TotalSeconds); - var ny = py - (vy * time.TotalSeconds); - - var dx = Math.Abs((int)px - (int)nx); - var dy = Math.Abs((int)py - (int)ny); - - if (dx >= 1.0 || dy >= 1.0) - { - // csak akkor kell ha csempevaltas volt - if (!_player.IsWalkable((int)nx, (int)ny)) - { - //_player.Zone.CreateAlignedDebugBeam(BeamType.red_20sec, new Position(nx, ny)); - break; - } - } - - px = nx; - py = ny; - //_player.Zone.CreateAlignedDebugBeam(BeamType.blue_20sec, new Position(px, py)); - } - - _player.CurrentPosition = new Position(px,py); - //_player.Zone.CreateAlignedDebugBeam(BeamType.orange_20sec, _player.CurrentPosition); - } - } - - public class ErrorPacketBuilder : IBuilder + public sealed class Player : Robot, IBlobableUnit, IBlobEmitter { - private readonly ErrorCodes _error; + private readonly IExtensionReader extensionReader; + private readonly ICorporationManager corporationManager; + private readonly MissionHandler.Factory missionHandlerFactory; + private readonly ITeleportStrategyFactories teleportStrategyFactories; + private readonly DockingBaseHelper dockingBaseHelper; + private readonly CombatLogger.Factory combatLoggerFactory; + private readonly IBlobEmitter blobEmitter; + private readonly BlobHandler blobHandler; + private readonly PlayerMovement movement; + private readonly IntervalTimer combatTimer = new IntervalTimer(TimeSpan.FromSeconds(10)); + private CombatLogger combatLogger; + private PlayerMoveCheckQueue check; + private CancellableDespawnHelper despawnHelper; - public ErrorPacketBuilder(ErrorCodes error) - { - _error = error; - } - - public Packet Build() - { - return new Packet(ZoneCommand.Error) {Error = _error}; - } - } - - public sealed class Player : Robot,IBlobableUnit,IBlobEmitter - { - private readonly IExtensionReader _extensionReader; - private readonly ICorporationManager _corporationManager; - private readonly MissionHandler.Factory _missionHandlerFactory; - private readonly ITeleportStrategyFactories _teleportStrategyFactories; - private readonly DockingBaseHelper _dockingBaseHelper; - private readonly CombatLogger.Factory _combatLoggerFactory; - private readonly IBlobEmitter _blobEmitter; - private readonly BlobHandler _blobHandler; - private readonly PlayerMovement _movement; - private CombatLogger _combatLogger; - - public Player(IExtensionReader extensionReader,ICorporationManager corporationManager,MissionHandler.Factory missionHandlerFactory,ITeleportStrategyFactories teleportStrategyFactories,DockingBaseHelper dockingBaseHelper,CombatLogger.Factory combatLoggerFactory) - { - _extensionReader = extensionReader; - _corporationManager = corporationManager; - _missionHandlerFactory = missionHandlerFactory; - _teleportStrategyFactories = teleportStrategyFactories; - _dockingBaseHelper = dockingBaseHelper; - _combatLoggerFactory = combatLoggerFactory; - Session = ZoneSession.None; - _movement = new PlayerMovement(this); + public static readonly TimeSpan NormalUndockDelay = TimeSpan.FromSeconds(7); + public const int ARKHE_REQUEST_TIMER_MINUTES_PVP = 3; + public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 1; - _blobEmitter = new BlobEmitter(this); - _blobHandler = new BlobHandler(this); - } + private bool HasAggressorEffect => EffectHandler.ContainsEffect(EffectType.effect_aggressor); public long CorporationEid { get; set; } + public IZoneSession Session { get; private set; } + public Character Character { get; set; } = Character.None; - public void SetSession(IZoneSession session) - { - Session = session; - } + public bool HasGMStealth { get; set; } public MissionHandler MissionHandler { get; private set; } - private bool HasAggressorEffect - { - get { return EffectHandler.ContainsEffect(EffectType.effect_aggressor); } - } - - public bool HasSelfTeleportEnablerEffect - { - get { return EffectHandler.ContainsEffect(EffectType.effect_teleport_self_enabler); } - } + public bool HasSelfTeleportEnablerEffect => EffectHandler.ContainsEffect(EffectType.effect_teleport_self_enabler); public Gang Gang { get; set; } @@ -180,11 +90,12 @@ public bool IsInSafeArea { get { - var zone = Zone; - if (zone == null) - return false; + IZone zone = Zone; - return zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area) || EffectHandler.ContainsEffect(EffectType.effect_safe_spot); + return zone != null +&& (zone.Configuration.Protected || + EffectHandler.ContainsEffect(EffectType.effect_syndicate_area) || + EffectHandler.ContainsEffect(EffectType.effect_safe_spot)); } } @@ -192,65 +103,73 @@ public override bool IsLockable { get { - var isInvulnerable = IsInvulnerable; + bool isInvulnerable = IsInvulnerable; - if (isInvulnerable) - return false; - - return base.IsLockable; + return !isInvulnerable && base.IsLockable; } } - public IBlobHandler BlobHandler - { - get { return _blobHandler; } - } - - public double BlobEmission - { - get { return _blobEmitter.BlobEmission; } - } + public IBlobHandler BlobHandler => blobHandler; - public double BlobEmissionRadius - { - get { return _blobEmitter.BlobEmissionRadius; } - } + public double BlobEmission => blobEmitter.BlobEmission; + public double BlobEmissionRadius => blobEmitter.BlobEmissionRadius; - public override void AcceptVisitor(IEntityVisitor visitor) + public Player( + IExtensionReader extensionReader, + ICorporationManager corporationManager, + MissionHandler.Factory missionHandlerFactory, + ITeleportStrategyFactories teleportStrategyFactories, + DockingBaseHelper dockingBaseHelper, + CombatLogger.Factory combatLoggerFactory) { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); + this.extensionReader = extensionReader; + this.corporationManager = corporationManager; + this.missionHandlerFactory = missionHandlerFactory; + this.teleportStrategyFactories = teleportStrategyFactories; + this.dockingBaseHelper = dockingBaseHelper; + this.combatLoggerFactory = combatLoggerFactory; + Session = ZoneSession.None; + movement = new PlayerMovement(this); + + blobEmitter = new BlobEmitter(this); + blobHandler = new BlobHandler(this); } - protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + public void EnableSelfTeleport(TimeSpan duration, int affectedZoneId = -1) { - base.OnEnterZone(zone, enterType); //aa + if (affectedZoneId != -1 && Zone.Id != affectedZoneId) + { + return; + } - zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Visible, this)); - - MissionHandler = _missionHandlerFactory(zone, this); - MissionHandler.InitMissions(); + ApplySelfTeleportEnablerEffect(duration); + } - Direction = FastRandom.NextDouble(); + public bool TryMove(Position position) + { + if (!IsWalkable(position)) + { + return false; + } - var p = DynamicProperties.GetProperty(k.pvpRemaining); - if (!p.HasValue) - return; + check.EnqueueMove(position); + CurrentPosition = position; - ApplyPvPEffect(TimeSpan.FromMilliseconds(p.Value)); - p.Clear(); + return true; } - protected override void OnRemovedFromZone(IZone zone) + public void SetSession(IZoneSession session) { - Session.SendPacket(ExitPacketBuilder); - zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); - - if (!States.LocalTeleport) - Session.Stop(); + Session = session; + } - base.OnRemovedFromZone(zone); + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } public override void OnUpdateToDb() @@ -263,26 +182,34 @@ public override void OnUpdateToDb() DynamicProperties.Update(k.currentCore, Core); } - var zone = Zone; + IZone zone = Zone; + if (zone == null || States.Dead) + { return; + } + + Character character = Character; - var character = Character; character.ZoneId = zone.Id; character.ZonePosition = CurrentPosition; - var p = DynamicProperties.GetProperty(k.pvpRemaining); + IDynamicProperty p = DynamicProperties.GetProperty(k.pvpRemaining); + Effect pvpEffect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); - var pvpEffect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); if (pvpEffect == null) { p.Clear(); + return; } - var effectTimer = pvpEffect.Timer; + IntervalTimer effectTimer = pvpEffect.Timer; + if (effectTimer != null) + { p.Value = (int)effectTimer.Remaining.TotalMilliseconds; + } } finally { @@ -290,62 +217,74 @@ public override void OnUpdateToDb() } } - protected override void OnUpdate(TimeSpan time) + public void SetStrongholdDespawn(TimeSpan time, UnitDespawnStrategy strategy) { - base.OnUpdate(time); - UpdateCombat(time); - _movement.Update(time); - _blobHandler.Update(time); - MissionHandler.Update(time); + if (despawnHelper == null) + { + despawnHelper = CancellableDespawnHelper.Create(this, time); + despawnHelper.DespawnStrategy = strategy; + } + } - _combatLogger?.Update(time); + public void ClearStrongholdDespawn() + { + despawnHelper?.Cancel(this); + despawnHelper = null; } public void SendModuleProcessError(Module module, ErrorCodes error) { - var packet = new Packet(ZoneCommand.ModuleEvaluateError); + Packet packet = new Packet(ZoneCommand.ModuleEvaluateError); packet.AppendByte((byte)module.ParentComponent.Type); - packet.AppendByte((byte) module.Slot); + packet.AppendByte((byte)module.Slot); packet.AppendInt((int)error); - Session.SendPacket(packet); } public void ApplyInvulnerableEffect() { - var builder = NewEffectBuilder().SetType(EffectType.effect_invulnerable); + RemoveInvulnerableEffect(); // Remove existing effect, set new + EffectBuilder builder = NewEffectBuilder().SetType(EffectType.effect_invulnerable); + _ = builder.WithDurationModifier(0.75); //Reduce span of syndicate protection ApplyEffect(builder); } + public void RemoveInvulnerableEffect() + { + EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); + } + public void ApplyTeleportSicknessEffect() { - var zone = Zone; + IZone zone = Zone; + if (zone == null || zone is TrainingZone) + { return; + } - var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_teleport_sickness); + EffectBuilder effectBuilder = NewEffectBuilder().SetType(EffectType.effect_teleport_sickness); if (HasPvpEffect) { - effectBuilder.WithDurationModifier(3.0); + _ = effectBuilder.WithDurationModifier(3.0); } ApplyEffect(effectBuilder); } - private void ApplyAggressorEffect() - { - ApplyEffect(NewEffectBuilder().SetType(EffectType.effect_aggressor)); - } - public void ApplySelfTeleportEnablerEffect(TimeSpan duration) { - var effect = EffectHandler.GetEffectsByType(EffectType.effect_teleport_self_enabler).FirstOrDefault(); + Effect effect = EffectHandler.GetEffectsByType(EffectType.effect_teleport_self_enabler).FirstOrDefault(); + if (effect != null) + { EffectHandler.Remove(effect); + } + + EffectBuilder builder = NewEffectBuilder().SetType(EffectType.effect_teleport_self_enabler).WithDuration(duration); - var builder = NewEffectBuilder().SetType(EffectType.effect_teleport_self_enabler).WithDuration(duration); ApplyEffect(builder); } @@ -356,38 +295,40 @@ public void RemoveSelfTeleportEnablerEffect() public void CheckDockingConditionsAndThrow(long baseEid, bool checkRange = true) { - if ( !Session.AccessLevel.IsAdminOrGm()) + if (!Session.AccessLevel.IsAdminOrGm()) { HasAggressorEffect.ThrowIfTrue(ErrorCodes.NotAllowedForAggressors); HasPvpEffect.ThrowIfTrue(ErrorCodes.CantDockThisState); HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.CantDockThisState); } - var zone = Zone; + IZone zone = Zone; + if (zone == null) + { return; + } + + DockingBase dockingBase = dockingBaseHelper.GetDockingBase(baseEid); - var dockingBase = _dockingBaseHelper.GetDockingBase(baseEid); if (dockingBase == null) + { return; + } - if (dockingBase.Zone == zone) + if (dockingBase.Zone == zone && checkRange) { - // csak akkor van range check ha a terepen van - if (checkRange) - { - // alapbol van checkrange - dockingBase.IsInDockingRange(this).ThrowIfFalse(ErrorCodes.DockingOutOfRange); - } + dockingBase.IsInDockingRange(this).ThrowIfFalse(ErrorCodes.DockingOutOfRange); } - var currentAccess = Session.AccessLevel; + AccessLevel currentAccess = Session.AccessLevel; + if (!currentAccess.IsAdminOrGm()) { - dockingBase.IsDockingAllowed(Character).ThrowIfError(); + _ = dockingBase.IsDockingAllowed(Character).ThrowIfError(); } - DockToBase(zone,dockingBase); + DockToBase(zone, dockingBase); } /// @@ -399,474 +340,490 @@ public void DockToBase(IZone zone, DockingBase dockingBase) { States.Dock = true; - var publicContainer = dockingBase.GetPublicContainer(); + PublicContainer publicContainer = dockingBase.GetPublicContainer(); FullArmorRepair(); publicContainer.AddItem(this, false); publicContainer.Save(); - dockingBase.DockIn(Character,NormalUndockDelay,ZoneExitType.Docked); + dockingBase.DockIn(Character, NormalUndockDelay, ZoneExitType.Docked); Transaction.Current.OnCommited(() => { RemoveFromZone(); MissionHelper.MissionAdvanceDockInTarget(Character.Id, zone.Id, CurrentPosition); - TransportAssignment.DeliverTransportAssignmentAsync(Character); + _ = TransportAssignment.DeliverTransportAssignmentAsync(Character); }); } - public static readonly TimeSpan NormalUndockDelay = TimeSpan.FromSeconds(7); + public ErrorCodes CheckPvp() + { + IZone zone = Zone; + Debug.Assert(zone != null); - protected override void OnDead(Unit killer) - { - HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); + return !HasPvpEffect && (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area)) + ? ErrorCodes.PvpIsNotAllowed + : ErrorCodes.NoError; } - private Task HandlePlayerDeadAsync(IZone zone, Unit killer) + public bool IsInDefaultCorporation() { - return Task.Run(() => HandlePlayerDead(zone, killer)); + return DefaultCorporationDataCache.IsCorporationDefault(CorporationEid); } - public const int ARKHE_REQUEST_TIMER_MINUTES_PVP = 10; - public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 5; - - - //ennek mindenkepp vegig kell futnia - private void HandlePlayerDead(IZone zone, Unit killer) + public override ItemPropertyModifier GetPropertyModifier(AggregateField field) { - using (var scope = Db.CreateTransaction()) - { - EnlistTransaction(); - try - { - killer = zone.ToPlayerOrGetOwnerPlayer(killer) ?? killer; + ItemPropertyModifier modifier = base.GetPropertyModifier(field); - SaveCombatLog(zone, killer); + if (Character == Character.None) + { + return modifier; + } - var character = Character; + CharacterExtensionCollection characterExtensions = Character.GetExtensions(); + System.Collections.Immutable.ImmutableDictionary extensions = extensionReader.GetExtensions(); + double extensionBonus = characterExtensions + .Select(e => extensions[e.id]) + .Where(e => e.aggregateField == field) + .Sum(e => characterExtensions.GetLevel(e.id) * e.bonus); - var dockingBase = character.GetHomeBaseOrCurrentBase(); - dockingBase.DockIn(character,NormalUndockDelay, ZoneExitType.Died); + extensionBonus += ExtensionBonuses + .Where(e => e.aggregateField == field) + .Sum(e => characterExtensions.GetLevel(e.extensionId) * e.bonus); - PlayerDeathLogger.Log.Write(zone, this, killer); + if (!extensionBonus.IsZero()) + { + ItemPropertyModifier m = ItemPropertyModifier.Create(field, extensionBonus); - //pay out insurance if needed - var wasInsured = InsuranceHelper.CheckInsuranceOnDeath(Eid, Definition); + m.NormalizeExtensionBonus(); + m.Modify(ref modifier); + } - if (!Session.AccessLevel.IsAdminOrGm()) - { - var robotInventory = GetContainer(); - Debug.Assert(robotInventory != null); + return modifier; + } - var lootItems = new List(); + public override void OnAggression(Unit victim) + { + base.OnAggression(victim); + AddInCombatWith(victim); - // minden fittelt modul - foreach (var module in Modules.Where(m => LootHelper.Roll())) - { - lootItems.Add(LootItemBuilder.Create(module).AsDamaged().Build()); + if (victim is ITaggable taggable) + { + taggable.Tag(this, TimeSpan.Zero); + } - var activeModule = module as ActiveModule; - var ammo = activeModule?.GetAmmo(); - if (ammo != null) - { - if (LootHelper.Roll()) - lootItems.Add(LootItemBuilder.Create(ammo).Build()); - } + if (IsUnitPVPAggro(victim)) + { + ApplyPvPEffect(); - // szedjuk le a robotrol - module.Parent = robotInventory.Eid; //in case the container is full + return; + } - //toroljuk is le, nem kell ez sehova mar - Repository.Delete(module); - } + if (!(victim is Player victimPlayer)) + { + return; + } - foreach (var item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) - { - //Transport assignments - var wrapper = item as VolumeWrapperContainer; - if (wrapper == null) - continue; + victimPlayer.Session.CancelLogout(); + ApplyPvPEffect(); - lootItems.AddRange(wrapper.GetLootItems()); - wrapper.SetAllowDelete(); - Repository.Delete(wrapper); - } + if (IsInSameCorporation(victimPlayer)) + { + return; + } - // elkerunk minden itemet a kontenerbol es valogatunk belole 50% szerint - foreach (var item in robotInventory.GetItems().Where(i => LootHelper.Roll() && !i.ED.AttributeFlags.NonStackable)) - { - var qtyMod = FastRandom.NextDouble(); - item.Quantity = (int)(item.Quantity * qtyMod); + if (HasPvpEffect && victimPlayer.HasPvpEffect) + { + return; + } + } - if (item.Quantity > 0) - { - lootItems.Add(LootItemBuilder.Create(item.Definition).SetQuantity(item.Quantity).SetRepackaged(item.ED.AttributeFlags.Repackable).Build()); - } - else - { - robotInventory.RemoveItemOrThrow(item); + public void OnPvpSupport(Unit target) + { + if (target is Player player && player.HasPvpEffect) + { + ApplyPvPEffect(); + } + } - //toroljuk is le, nem kell ez mar - Repository.Delete(item); - } - } + public override string InfoString => $"Player:{Character.Id}:{Definition}:{Eid}"; - if (lootItems.Count > 0) - { - var lootContainer = LootContainer.Create().AddLoot(lootItems).BuildAndAddToZone(zone, CurrentPosition); + public void SendInitSelf() + { + IZone zone = Zone; - if (lootContainer != null) - { - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.PutLoot).SetCharacter(character).SetContainer(lootContainer.Eid); - foreach (var lootItem in lootItems) - { - b.SetItem(lootItem.ItemInfo.Definition,lootItem.ItemInfo.Quantity); - Character.LogTransaction(b); - } - } - } + Debug.Assert(zone != null, "zone != null"); + Session.SendPacket(EnterPacketBuilder); + Session.SendTerrainData(); + zone.SendBeamsToPlayer(this, GridDistricts.All); - var killedByPlayer = (killer != null && killer.IsPlayer()); + IEnumerable lockPackets = GetLockPackets(); - Trashcan.Get().MoveToTrash(this,Session.DisconnectTime, wasInsured, killedByPlayer,Session.InactiveTime); + Session.SendPackets(lockPackets); - character.NextAvailableRobotRequestTime = DateTime.Now.AddMinutes(killedByPlayer ? ARKHE_REQUEST_TIMER_MINUTES_PVP : ARKHE_REQUEST_TIMER_MINUTES_NPC); + foreach (IUnitVisibility visibility in GetVisibleUnits()) + { + Session.SendPacket(visibility.Target.EnterPacketBuilder); - Robot activeRobot = null; + if (!(visibility.Target is Robot robot)) + { + continue; + } - if (!killedByPlayer) - { - activeRobot = dockingBase.CreateStarterRobotForCharacter(character); + IEnumerable unitLockPackets = robot.GetLockPackets(); - if (activeRobot != null) - { - Transaction.Current.OnCommited(() => - { - var starterRobotInfo = new Dictionary - { - {k.baseEID, Eid}, - {k.robotEID, activeRobot.Eid} - }; + Session.SendPackets(unitLockPackets); + } - Message.Builder.SetCommand(Commands.StarterRobotCreated).WithData(starterRobotInfo).ToCharacter(character).Send(); - }); - } - } + Session.SendPacket(new GangUpdatePacketBuilder(Visibility.Visible, zone.GetGangMembers(Gang))); + Session.SendPacket(zone.Weather.GetCurrentWeather().CreateUpdatePacket()); - character.SetActiveRobot(activeRobot); - } - else - { - // mert rendesek vagyunk - this.Repair(); + foreach (Effect effect in EffectHandler.Effects) + { + Session.SendPacket(new EffectPacketBuilder(effect, true)); + } - // csak az adminok miatt kell - var container = dockingBase.GetPublicContainer(); - container.AddItem(this, false); - } + foreach (ActiveModule module in ActiveModules) + { + module.ForceUpdate(); + } + } - this.Save(); + public void WriteFQLog(string message) + { + LogEvent e = new LogEvent + { + LogType = LogType.Info, + Tag = "FQ", + Message = $"{InfoString} - {message}", + }; - scope.Complete(); + Logger.Log(e); + } + + public void SendForceUpdate() + { + Session.SendPacket(new UnitUpdatePacketBuilder(this, UpdatePacketControl.ForceReposition)); + } + + public override void OnCombatEvent(Unit source, CombatEventArgs e) + { + base.OnCombatEvent(source, e); + + Player player = Zone.ToPlayerOrGetOwnerPlayer(source); + + if (player == null) + { + return; + } + + CombatLogger logger = LazyInitializer.EnsureInitialized(ref combatLogger, CreateCombatLogger); + + logger.Log(player, e); + } + + public static Player LoadPlayerAndAddToZone(IZone zone, Character character) + { + using (TransactionScope scope = Db.CreateTransaction()) + { + Player player = (Player)character.GetActiveRobot().ThrowIfNull(ErrorCodes.ARobotMustBeSelected); + + DockingBase dockingBase = null; + ZoneEnterType zoneEnterType; + Position spawnPosition; + + if (character.IsDocked) + { + zoneEnterType = ZoneEnterType.Undock; + dockingBase = character.GetCurrentDockingBase(); + spawnPosition = UndockSpawnPositionSelector.SelectSpawnPosition(dockingBase); + character.ZoneId = zone.Id; + character.ZonePosition = spawnPosition; + character.IsDocked = false; } - catch (Exception ex) + else { - Logger.Exception(ex); + zoneEnterType = ZoneEnterType.Teleport; + _ = zone.Id.ThrowIfNotEqual(character.ZoneId ?? -1, ErrorCodes.InvalidZoneId); + + Position? zonePosition = character.ZonePosition.ThrowIfNull(ErrorCodes.InvalidPosition); + + spawnPosition = (Position)zonePosition; } + + spawnPosition = zone.FixZ(spawnPosition); + + ClosestWalkablePositionFinder finder = new ClosestWalkablePositionFinder(zone, spawnPosition, player); + Position validPosition = finder.FindOrThrow(); + + ZoneStorage zoneStorage = zone.Configuration.GetStorage(); + + player.Parent = zoneStorage.Eid; + player.FullCoreRecharge(); + player.Save(); + + Transaction.Current.OnCommited(() => + { + dockingBase?.LeaveChannel(character); + player.CorporationEid = character.CorporationEid; + zone.SetGang(player); + player.AddToZone(zone, validPosition, zoneEnterType); + player.ApplyInvulnerableEffect(); + }); + + scope.Complete(); + + return player; } } - protected override void OnTileChanged() + [CanBeNull] + public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, bool applyInvulnerable) { - base.OnTileChanged(); + IZone zone = Zone; - var zone = Zone; - if (zone == null) - return; + if (zone == null) + { + return null; + } - MissionHandler?.MissionUpdateOnTileChange(); + TeleportWithinZone teleport = teleportStrategyFactories.TeleportWithinZoneFactory(); - var controlInfo = zone.Terrain.Controls.GetValue(CurrentPosition); + if (teleport == null) + { + return null; + } - ApplyHighwayEffect(controlInfo.IsAnyHighway); + teleport.TargetPosition = target; + teleport.ApplyTeleportSickness = applyTeleportSickness; + teleport.ApplyInvulnerable = applyInvulnerable; - if (zone.Configuration.Protected) - return; + Task task = teleport.DoTeleportAsync(this); - //PVP zone - ApplySyndicateAreaEffect(controlInfo.SyndicateArea); + return task?.LogExceptions(); } - protected override void OnCellChanged(CellCoord lastCellCoord, CellCoord currentCellCoord) + public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) { - base.OnCellChanged(lastCellCoord,currentCellCoord); + Dictionary data = unit.BaseInfoToDictionary(); - var zone = Zone; - if ( zone == null ) - return; + data.Add(k.timeOut, (int)timeout.TotalMilliseconds); + data.Add(k.started, (long)start.TotalMilliseconds); + data.Add(k.now, (long)start.TotalMilliseconds); + Message.Builder + .SetCommand(Commands.AlarmStart) + .WithData(data) + .ToCharacter(Character) + .Send(); + } - Task.Run(() => - { - var district = currentCellCoord.ComputeDistrict(lastCellCoord); - zone.SendBeamsToPlayer(this,district); - Session.SendTerrainData(); - }).LogExceptions(); + public void SendEndProgressBar(Unit unit, bool success = true) + { + Dictionary info = unit.BaseInfoToDictionary(); + + info.Add(k.success, success); + Message.Builder.SetCommand(Commands.AlarmOver).WithData(info).ToCharacter(Character).Send(); } - public ErrorCodes CheckPvp() + public void SendArtifactRadarBeam(Position targetPosition) { - var zone = Zone; - Debug.Assert(zone != null); + BeamBuilder builder = Beam.NewBuilder() + .WithType(BeamType.artifact_radar) + .WithSourcePosition(targetPosition) + .WithTargetPosition(targetPosition) + .WithState(BeamState.AlignToTerrain) + .WithDuration(TimeSpan.FromSeconds(30)); - if (!HasPvpEffect && (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area))) - return ErrorCodes.PvpIsNotAllowed; + Session.SendBeam(builder); + } - return ErrorCodes.NoError; + public bool IsStandingMatch(long targetCorporationEid, double? standingLimit) + { + return corporationManager.IsStandingMatch(targetCorporationEid, CorporationEid, standingLimit); } - private void ApplySyndicateAreaEffect(bool apply) + public void ReloadContainer() { - if ( apply ) + if (Transaction.Current != null) { - var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_syndicate_area); - ApplyEffect(effectBuilder); + Reload(); } else { - EffectHandler.RemoveEffectsByType(EffectType.effect_syndicate_area); + using (TransactionScope scope = Db.CreateTransaction()) + { + Reload(); + scope.Complete(); + } } } - private void ApplyHighwayEffect(bool apply) + public void UpdateCorporationOnZone(long newCorporationEid) { - if (apply) + CorporationEid = newCorporationEid; + + Character[] playersOnZone = Zone.GetCharacters().ToArray(); + + if (playersOnZone.Length <= 0) { - var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_highway) - .WithPropertyModifier(ItemPropertyModifier.Create(AggregateField.effect_speed_highway_modifier, 1.0)); - ApplyEffect(effectBuilder); + return; } - else + + Dictionary result = new Dictionary { - EffectHandler.RemoveEffectsByType(EffectType.effect_highway); - } - } + {k.corporationEID, newCorporationEid}, + {k.characterID, Character.Id}, + }; - private bool IsInSameCorporation(Player player) - { - return (CorporationEid == player.CorporationEid) && !IsInDefaultCorporation(); + Message.Builder.SetCommand(Commands.CharacterUpdate).WithData(result).ToCharacters(playersOnZone).Send(); } - public bool IsInDefaultCorporation() + public override void UpdateVisibilityOf(Unit target) { - return DefaultCorporationDataCache.IsCorporationDefault(CorporationEid); + target.UpdatePlayerVisibility(this); } - public override ItemPropertyModifier GetPropertyModifier(AggregateField field) + public void SetCombatState(bool state) { - var modifier = base.GetPropertyModifier(field); + States.Combat = state; + combatTimer.Reset(); - if (Character == Character.None) - return modifier; + if (state) + { + Session.ResetLogoutTimer(); + } + } - var characterExtensions = Character.GetExtensions(); - var extensions = _extensionReader.GetExtensions(); + public void AddInCombatWith(Unit enemy) + { + SetCombatState(true); - var extensionBonus = characterExtensions.Select(e => extensions[e.id]) - .Where(e => e.aggregateField == field) - .Sum(e => characterExtensions.GetLevel(e.id) * e.bonus); + Player enemyPlayer = enemy as Player; - extensionBonus += ExtensionBonuses.Where(e => e.aggregateField == field).Sum(e => characterExtensions.GetLevel(e.extensionId) * e.bonus); + enemyPlayer?.SetCombatState(true); + } - if (!extensionBonus.IsZero()) - { - var m = ItemPropertyModifier.Create(field,extensionBonus); - m.NormalizeExtensionBonus(); - m.Modify(ref modifier); - } + public bool IsInSameCorporation(Player player) + { + return (CorporationEid == player.CorporationEid) && !IsInDefaultCorporation(); + } - return modifier; + public bool IsUnitPVPAggro(Unit unit) + { + return unit is MobileTeleport || + unit is IPBSObject || + unit is WallHealer || + unit is ProximityDeviceBase || + (unit is BlobEmitterUnit b && b.IsPlayerSpawned) || + (unit is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player); } - public override void OnAggression(Unit victim) + protected override void OnDead(Unit killer) { - base.OnAggression(victim); + _ = HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); + } - AddInCombatWith(victim); + protected override void OnTileChanged() + { + base.OnTileChanged(); - if (victim is ITaggable taggable) - taggable.Tag(this,TimeSpan.Zero); + IZone zone = Zone; - if ( victim is MobileTeleport || victim is BlobEmitterUnit || victim is IPBSObject || victim is WallHealer || victim is ProximityProbeBase) + if (zone == null) { - ApplyPvPEffect(); return; } - if (!(victim is Player victimPlayer)) - return; + MissionHandler?.MissionUpdateOnTileChange(); - victimPlayer.Session.CancelLogout(); + TerrainControlInfo controlInfo = zone.Terrain.Controls.GetValue(CurrentPosition); - ApplyPvPEffect(); + ApplyHighwayEffect(controlInfo.IsAnyHighway); - if (IsInSameCorporation(victimPlayer)) + if (zone.Configuration.Protected) + { return; + } - if (HasPvpEffect && victimPlayer.HasPvpEffect) - return; + ApplySyndicateAreaEffect(controlInfo.SyndicateArea); + } - var zone = Zone; - if (zone == null) - return; + protected override void OnCellChanged(CellCoord lastCellCoord, CellCoord currentCellCoord) + { + base.OnCellChanged(lastCellCoord, currentCellCoord); - if (zone.Configuration.Protected) + IZone zone = Zone; + + if (zone == null) { - // PROTECTED ISLANDS - if (!victimPlayer.HasPvpEffect && !victimPlayer.HasAggressorEffect) - ApplyAggressorEffect(); + return; } - else + + _ = Task.Run(() => { - //PVP ISLANDS - if (victimPlayer.States.BigBrother && !victimPlayer.HasPvpEffect) - ApplyAggressorEffect(); - } + GridDistricts district = currentCellCoord.ComputeDistrict(lastCellCoord); + zone.SendBeamsToPlayer(this, district); + Session.SendTerrainData(); + }).LogExceptions(); } - public void OnPvpSupport(Unit target) + protected override void UpdateUnitVisibility(Unit target) { - if (target is Player player && player.HasPvpEffect) - { - ApplyPvPEffect(); - } + UpdateVisibility(target); } - public override string InfoString + protected internal override void UpdatePlayerVisibility(Player player) { - get { return $"Player:{Character.Id}:{Definition}:{Eid}"; } + UpdateVisibility(player); } - public void SendInitSelf() - { - var zone = Zone; - Debug.Assert(zone != null, "zone != null"); - - Session.SendPacket(EnterPacketBuilder); - - // terrain - Session.SendTerrainData(); - - // beams - zone.SendBeamsToPlayer(this,GridDistricts.All); - - // elkuldjuk az osszes lockot - var lockPackets = GetLockPackets(); - Session.SendPackets(lockPackets); - - foreach (var visibility in GetVisibleUnits()) - { - Session.SendPacket(visibility.Target.EnterPacketBuilder); - - var robot = visibility.Target as Robot; - if (robot == null) - continue; - - var unitLockPackets = robot.GetLockPackets(); - Session.SendPackets(unitLockPackets); - } - - Session.SendPacket(new GangUpdatePacketBuilder(Visibility.Visible, zone.GetGangMembers(Gang))); - Session.SendPacket(zone.Weather.GetCurrentWeather().CreateUpdatePacket()); - - foreach (var effect in EffectHandler.Effects) - { - Session.SendPacket(new EffectPacketBuilder(effect, true)); - } - - foreach (var module in ActiveModules) - { - module.ForceUpdate(); - } - } - - public void WriteFQLog(string message) - { - var e = new LogEvent - { - LogType = LogType.Info, - Tag = "FQ", - Message = $"{InfoString} - {message}" - }; - - Logger.Log(e); - } - - private void SendError(ErrorCodes error) + protected override bool IsDetected(Unit target) { - Session.SendPacket(new ErrorPacketBuilder(error)); + return Gang.IsMember(target) || base.IsDetected(target); } - public void SendForceUpdate() + protected override bool IsHostileFor(Unit unit) { - Session.SendPacket(new UnitUpdatePacketBuilder(this, UpdatePacketControl.ForceReposition)); + return unit.IsHostile(this); } - protected override void OnBroadcastPacket(IBuilder packetBuilder) { base.OnBroadcastPacket(packetBuilder); Session.SendPacket(packetBuilder.Build()); } - protected override void OnUnitVisibilityUpdated(Unit target,Visibility visibility) + protected override void OnUnitVisibilityUpdated(Unit target, Visibility visibility) { switch (visibility) { case Visibility.Visible: - { - target.BroadcastPacket += OnUnitBroadcastPacket; - target.Updated += OnUnitUpdated; - Session.SendPacket(target.EnterPacketBuilder); - break; - } + { + target.BroadcastPacket += OnUnitBroadcastPacket; + target.Updated += OnUnitUpdated; + Session.SendPacket(target.EnterPacketBuilder); + + break; + } case Visibility.Invisible: - { - target.BroadcastPacket -= OnUnitBroadcastPacket; - target.Updated -= OnUnitUpdated; - Session.SendPacket(target.ExitPacketBuilder); - break; - } + { + target.BroadcastPacket -= OnUnitBroadcastPacket; + target.Updated -= OnUnitUpdated; + Session.SendPacket(target.ExitPacketBuilder); + + break; + } } if (Gang.IsMember(target)) { - Session.SendPacket(new GangUpdatePacketBuilder(visibility,(Player) target)); + Session.SendPacket(new GangUpdatePacketBuilder(visibility, (Player)target)); } - base.OnUnitVisibilityUpdated(target,visibility); - } - - private void OnUnitUpdated(Unit unit, UnitUpdatedEventArgs e) - { - if (!Gang.IsMember(unit)) - return; - - var send = (e.UpdateTypes & UnitUpdateTypes.Visibility) > 0 || (e.UpdatedProperties != null && e.UpdatedProperties.Any(p => p.Field.IsPublic())); - if (!send) - return; - - var v = Visibility.Invisible; - if (unit.InZone) - v = Visibility.Visible; - - Session.SendPacket(new GangUpdatePacketBuilder(v,(Player) unit)); - } - - private void OnUnitBroadcastPacket(Unit sender, Packet packet) - { - Session.SendPacket(packet); + base.OnUnitVisibilityUpdated(target, visibility); } protected override void OnLockStateChanged(Lock @lock) @@ -875,18 +832,13 @@ protected override void OnLockStateChanged(Lock @lock) if (@lock is UnitLock u) { - var player = u.Target as Player; + Player player = u.Target as Player; + player?.Session.CancelLogout(); if (@lock.State == LockState.Locked) { - // !! - // this will enqueue primary, secondary, and will also fire if a primary is converted to secondary - // !! - - var npc = u.Target as Npc; - - if (npc != null) + if (u.Target is Npc npc) { MissionHandler.EnqueueMissionEventInfo(new LockUnitEventInfo(this, npc, npc.CurrentPosition)); MissionHandler.SignalParticipationByLocking(npc.GetMissionGuid()); @@ -896,28 +848,32 @@ protected override void OnLockStateChanged(Lock @lock) if (@lock.State == LockState.Inprogress && EffectHandler.ContainsEffect(EffectType.effect_invulnerable)) { - EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); + EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); } } protected override void OnEffectChanged(Effect effect, bool apply) { - base.OnEffectChanged(effect,apply); + base.OnEffectChanged(effect, apply); - if (!apply) + if (!apply) + { return; + } switch (effect.Type) { case EffectType.effect_demobilizer: { OnCombatEvent(effect.Source, new DemobilizerEventArgs()); + break; } case EffectType.effect_sensor_supress: { - OnCombatEvent(effect.Source,new SensorDampenerEventArgs()); + OnCombatEvent(effect.Source, new SensorDampenerEventArgs()); + break; } } @@ -929,261 +885,327 @@ protected override void OnLockError(Lock @lock, ErrorCodes error) base.OnLockError(@lock, error); } - public override void OnCombatEvent(Unit source, CombatEventArgs e) + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { - base.OnCombatEvent(source, e); + base.OnEnterZone(zone, enterType); + check = PlayerMoveCheckQueue.Create(this, CurrentPosition); + zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Visible, this)); + MissionHandler = missionHandlerFactory(zone, this); + MissionHandler.InitMissions(); + Direction = FastRandom.NextDouble(); - var player = Zone.ToPlayerOrGetOwnerPlayer(source); - if (player == null) + IDynamicProperty p = DynamicProperties.GetProperty(k.pvpRemaining); + + if (!p.HasValue) + { return; + } - var logger = LazyInitializer.EnsureInitialized(ref _combatLogger, CreateCombatLogger); - logger.Log(player,e); + ApplyPvPEffect(TimeSpan.FromMilliseconds(p.Value)); + p.Clear(); } - private CombatLogger CreateCombatLogger() + protected override void OnRemovedFromZone(IZone zone) { - var logger = _combatLoggerFactory(this); - logger.Expired = () => + Session.SendPacket(ExitPacketBuilder); + zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); + check.StopAndDispose(); + + if (!States.LocalTeleport) { - _combatLogger = null; - }; - return logger; + Session.Stop(); + } + + base.OnRemovedFromZone(zone); } - private void SaveCombatLog(IZone zone, Unit killer) + protected override void OnUpdate(TimeSpan time) { - _combatLogger?.Save(zone,killer); + base.OnUpdate(time); + UpdateCombat(time); + movement.Update(time); + blobHandler.Update(time); + MissionHandler.Update(time); + combatLogger?.Update(time); + despawnHelper?.Update(time, this); } - public static Player LoadPlayerAndAddToZone(IZone zone,Character character) + private void OnUnitUpdated(Unit unit, UnitUpdatedEventArgs e) { - using (var scope = Db.CreateTransaction()) + if (!Gang.IsMember(unit)) { - var player = (Player)character.GetActiveRobot().ThrowIfNull(ErrorCodes.ARobotMustBeSelected); - - DockingBase dockingBase = null; - - ZoneEnterType zoneEnterType; - Position spawnPosition; - if (character.IsDocked) - { - // ha bazisrol jott - zoneEnterType = ZoneEnterType.Undock; + return; + } - dockingBase = character.GetCurrentDockingBase(); - spawnPosition = UndockSpawnPositionSelector.SelectSpawnPosition(dockingBase); + bool send = (e.UpdateTypes & UnitUpdateTypes.Visibility) > 0 || (e.UpdatedProperties != null && e.UpdatedProperties.Any(p => p.Field.IsPublic())); - character.ZoneId = zone.Id; - character.ZonePosition = spawnPosition; - character.IsDocked = false; - } - else - { - // ha teleportalt - zoneEnterType = ZoneEnterType.Teleport; + if (!send) + { + return; + } - zone.Id.ThrowIfNotEqual(character.ZoneId ?? -1, ErrorCodes.InvalidZoneId); + Visibility v = Visibility.Invisible; - var zonePosition = character.ZonePosition.ThrowIfNull(ErrorCodes.InvalidPosition); - spawnPosition = (Position)zonePosition; - } + if (unit.InZone) + { + v = Visibility.Visible; + } - spawnPosition = zone.FixZ(spawnPosition); + Session.SendPacket(new GangUpdatePacketBuilder(v, (Player)unit)); + } - // keresunk neki valami jo poziciot - var finder = new ClosestWalkablePositionFinder(zone, spawnPosition, player); - var validPosition = finder.FindOrThrow(); + private void OnUnitBroadcastPacket(Unit sender, Packet packet) + { + Session.SendPacket(packet); + } - // parentoljuk a zonahoz <<< NAGYON FONTOS - TILOS MASHOGY kulonben bennmaradnak a (pbs) bazison a robotok, es pl letorlodnek amikor kilovik a bazist - var zoneStorage = zone.Configuration.GetStorage(); - player.Parent = zoneStorage.Eid; - player.FullCoreRecharge(); + private void UpdateCombat(TimeSpan time) + { + if (!States.Combat) + { + return; + } - // elmentjuk - player.Save(); + _ = combatTimer.Update(time); - // csak akkor rakjuk ki ha volt rendes commit - Transaction.Current.OnCommited(() => - { - dockingBase?.LeaveChannel(character); + if (!combatTimer.Passed) + { + return; + } - player.CorporationEid = character.CorporationEid; - zone.SetGang(player); + SetCombatState(false); + } - player.AddToZone(zone,validPosition,zoneEnterType); - - player.ApplyInvulnerableEffect(); - }); + private void Reload() + { + RobotInventory container = GetContainer(); - scope.Complete(); - return player; - } + Debug.Assert(container != null, "container != null"); + container.EnlistTransaction(); + container.ReloadItems(Character); + container.SendUpdateToOwner(); } - [CanBeNull] - public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, bool applyInvulnerable) + private CombatLogger CreateCombatLogger() { - var zone = Zone; - if (zone == null) - return null; + CombatLogger logger = combatLoggerFactory(this); - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); - if (teleport == null) - return null; + logger.Expired = () => + { + combatLogger = null; + }; - teleport.TargetPosition = target; - teleport.ApplyTeleportSickness = applyTeleportSickness; - teleport.ApplyInvulnerable = applyInvulnerable; - var task = teleport.DoTeleportAsync(this); - return task?.LogExceptions(); + return logger; } - public override void UpdateVisibilityOf(Unit target) + private void SaveCombatLog(IZone zone, Unit killer) { - target.UpdatePlayerVisibility(this); + combatLogger?.Save(zone, killer); } - protected override void UpdateUnitVisibility(Unit target) + private void ApplySyndicateAreaEffect(bool apply) { - UpdateVisibility(target); + if (apply) + { + EffectBuilder effectBuilder = NewEffectBuilder().SetType(EffectType.effect_syndicate_area); + ApplyEffect(effectBuilder); + } + else + { + EffectHandler.RemoveEffectsByType(EffectType.effect_syndicate_area); + } } - protected internal override void UpdatePlayerVisibility(Player player) + private void ApplyHighwayEffect(bool apply) { - UpdateVisibility(player); + if (apply) + { + EffectBuilder effectBuilder = NewEffectBuilder() + .SetType(EffectType.effect_highway) + .WithPropertyModifier(ItemPropertyModifier.Create(AggregateField.effect_speed_highway_modifier, 1.0)); + + ApplyEffect(effectBuilder); + } + else + { + EffectHandler.RemoveEffectsByType(EffectType.effect_highway); + } } - protected override bool IsDetected(Unit target) + private void SendError(ErrorCodes error) { - if ( Gang.IsMember(target) ) - return true; - - return base.IsDetected(target); + Session.SendPacket(new ErrorPacketBuilder(error)); } - protected override bool IsHostileFor(Unit unit) + private Task HandlePlayerDeadAsync(IZone zone, Unit killer) { - return unit.IsHostile(this); + return Task.Run(() => HandlePlayerDead(zone, killer)); } - private void UpdateCombat(TimeSpan time) + private void HandlePlayerDead(IZone zone, Unit killer) { - if (!States.Combat) - return; + using (TransactionScope scope = Db.CreateTransaction()) + { + EnlistTransaction(); - _combatTimer.Update(time); + try + { + killer = zone.ToPlayerOrGetOwnerPlayer(killer) ?? killer; - if ( !_combatTimer.Passed) - return; + SaveCombatLog(zone, killer); - SetCombatState(false); - } + Character character = Character; + DockingBase dockingBase = character.GetHomeBaseOrCurrentBase(); - private void SetCombatState(bool state) - { - States.Combat = state; - _combatTimer.Reset(); + dockingBase.DockIn(character, NormalUndockDelay, ZoneExitType.Died); - if ( state ) - Session.ResetLogoutTimer(); - } + PlayerDeathLogger.Log.Write(zone, this, killer); - private readonly IntervalTimer _combatTimer = new IntervalTimer(TimeSpan.FromSeconds(10)); + bool wasInsured = InsuranceHelper.CheckInsuranceOnDeath(Eid, Definition); - private void AddInCombatWith(Unit enemy) - { - SetCombatState(true); + if (!Session.AccessLevel.IsAdminOrGm()) + { + RobotInventory robotInventory = GetContainer(); - var enemyPlayer = enemy as Player; - enemyPlayer?.SetCombatState(true); - } + Debug.Assert(robotInventory != null); - public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) - { - var data = unit.BaseInfoToDictionary(); + List lootItems = new List(); - data.Add(k.timeOut, (int)timeout.TotalMilliseconds); - data.Add(k.started, (long)start.TotalMilliseconds); - data.Add(k.now, (long)start.TotalMilliseconds); + foreach (Module module in Modules.Where(m => LootHelper.Roll())) + { + lootItems.Add(LootItemBuilder.Create(module).AsDamaged().Build()); + ActiveModule activeModule = module as ActiveModule; + Items.Ammos.Ammo ammo = activeModule?.GetAmmo(); - Message.Builder.SetCommand(Commands.AlarmStart).WithData(data).ToCharacter(Character).Send(); - } + if (ammo != null && LootHelper.Roll()) + { + lootItems.Add(LootItemBuilder.Create(ammo).Build()); + } - public void SendEndProgressBar( Unit unit, bool success = true) - { - var info = unit.BaseInfoToDictionary(); - info.Add(k.success, success); - Message.Builder.SetCommand(Commands.AlarmOver).WithData(info).ToCharacter(Character).Send(); - } + module.Parent = robotInventory.Eid; - public void SendArtifactRadarBeam(Position targetPosition) - { - var builder = Beam.NewBuilder() - .WithType(BeamType.artifact_radar) - .WithSourcePosition(targetPosition) - .WithTargetPosition(targetPosition) - .WithState(BeamState.AlignToTerrain) - .WithDuration(TimeSpan.FromSeconds(30)); + Repository.Delete(module); + } - Session.SendBeam(builder); - } + foreach (Item item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) + { + if (!(item is VolumeWrapperContainer wrapper)) + { + continue; + } - public bool IsStandingMatch(long targetCorporationEid, double? standingLimit) - { - return _corporationManager.IsStandingMatch(targetCorporationEid, CorporationEid, standingLimit); - } + lootItems.AddRange(wrapper.GetLootItems()); + wrapper.SetAllowDelete(); + Repository.Delete(wrapper); + } - public void ReloadContainer() - { - void Reload() - { - var container = GetContainer(); - Debug.Assert(container != null,"container != null"); + foreach (Item item in robotInventory + .GetItems() + .Where(i => LootHelper.Roll() && !i.ED.AttributeFlags.NonStackable)) + { + double qtyMod = FastRandom.NextDouble(); - container.EnlistTransaction(); - container.ReloadItems(Character); - container.SendUpdateToOwner(); - } + item.Quantity = (int)(item.Quantity * qtyMod); - if (Transaction.Current != null) - Reload(); - else - { - using (var scope = Db.CreateTransaction()) - { - Reload(); - scope.Complete(); - } - } - } + if (item.Quantity > 0) + { + lootItems.Add( + LootItemBuilder + .Create(item.Definition) + .SetQuantity(item.Quantity) + .SetRepackaged(item.ED.AttributeFlags.Repackable) + .Build()); + } + else + { + robotInventory.RemoveItemOrThrow(item); + Repository.Delete(item); + } + } - public void UpdateCorporationOnZone(long newCorporationEid) - { - CorporationEid = newCorporationEid; + if (ED.Config.Tint != Tint && LootHelper.Roll(0.5)) + { + EntityDefault paint = EntityDefault.Reader.GetAll() + .Where(i => i.CategoryFlags == CategoryFlags.cf_paints) + .Where(i => i.Config.Tint == Tint).First(); - var playersOnZone = Zone.GetCharacters().ToArray(); - if (playersOnZone.Length <= 0) - return; + if (paint != null) + { + lootItems.Add(LootItemBuilder.Create(paint.Definition).SetQuantity(1).SetDamaged(false).Build()); + } + } - var result = new Dictionary - { - {k.corporationEID, newCorporationEid}, - {k.characterID, Character.Id}, - }; + if (lootItems.Count > 0) + { + LootContainer lootContainer = LootContainer.Create() + .AddLoot(lootItems) + .BuildAndAddToZone(zone, CurrentPosition); - Message.Builder.SetCommand(Commands.CharacterUpdate).WithData(result).ToCharacters(playersOnZone).Send(); - } + if (lootContainer != null) + { + TransactionLogEventBuilder b = TransactionLogEvent.Builder() + .SetTransactionType(TransactionType.PutLoot) + .SetCharacter(character) + .SetContainer(lootContainer.Eid); - public void EnableSelfTeleport(TimeSpan duration, int affectedZoneId = -1) - { - if (affectedZoneId != -1 && Zone.Id != affectedZoneId) - return; + foreach (LootItem lootItem in lootItems) + { + _ = b.SetItem(lootItem.ItemInfo.Definition, lootItem.ItemInfo.Quantity); + Character.LogTransaction(b); + } + } + } - ApplySelfTeleportEnablerEffect(duration); - } + bool killedByPlayer = killer != null && killer.IsPlayer(); + + Trashcan.Get() + .MoveToTrash(this, Session.DisconnectTime, wasInsured, killedByPlayer, Session.InactiveTime); + + character.NextAvailableRobotRequestTime = + DateTime.Now.AddMinutes(killedByPlayer ? ARKHE_REQUEST_TIMER_MINUTES_PVP : ARKHE_REQUEST_TIMER_MINUTES_NPC); + + Robot activeRobot = null; + + if (!killedByPlayer) + { + activeRobot = dockingBase.CreateStarterRobotForCharacter(character); + if (activeRobot != null) + { + Transaction.Current.OnCommited(() => + { + Dictionary starterRobotInfo = new Dictionary + { + {k.baseEID, Eid}, + {k.robotEID, activeRobot.Eid} + }; + Message.Builder.SetCommand(Commands.StarterRobotCreated).WithData(starterRobotInfo).ToCharacter(character).Send(); + }); + } + } + + character.SetActiveRobot(activeRobot); + } + else + { + this.Repair(); + + PublicContainer container = dockingBase.GetPublicContainer(); + + container.AddItem(this, false); + } + + Save(); + + scope.Complete(); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + } + } } } diff --git a/src/Perpetuum/Players/PlayerMoveChecker.cs b/src/Perpetuum/Players/PlayerMoveChecker.cs new file mode 100644 index 000000000..cbe8a64a1 --- /dev/null +++ b/src/Perpetuum/Players/PlayerMoveChecker.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Perpetuum.Log; +using Perpetuum.PathFinders; +using Perpetuum.Threading; +using Perpetuum.Zones; + +namespace Perpetuum.Players +{ + public class PlayerMoveCheckQueue : Disposable + { + private readonly TimeSpan MAX_TIMEOUT = TimeSpan.FromSeconds(2); + private readonly Task _task; + private readonly CancellationTokenSource _tokenSrc; + private CancellationToken _ct; + + private readonly Player _player; + private readonly PlayerMoveChecker _moveChecker; + private readonly BlockingCollection _movesToReview; + + private Position Prev { get; set; } + private bool IsCompleted { get { return _movesToReview?.IsAddingCompleted ?? true; } } + private bool IsCanceled { get { return _ct.IsCancellationRequested; } } + + public static PlayerMoveCheckQueue Create(Player player, Position start) + { + var pmcq = new PlayerMoveCheckQueue(player, start); + pmcq.Start(); + return pmcq; + } + + private PlayerMoveCheckQueue(Player player, Position start) + { + Prev = start; + _player = player; + _moveChecker = new PlayerMoveChecker(player); + _tokenSrc = new CancellationTokenSource(); + _movesToReview = new BlockingCollection(); + _ct = _tokenSrc.Token; + _task = new Task(() => ProcessQueue(), + TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness); + } + + private void Start() + { + if (_task.Status == TaskStatus.Created) + _task.Start(); + } + + public void StopAndDispose() + { + try + { + if (!Stop()) + { + Logger.Warning("PMCQ failed to join Task under timeout"); + } + Dispose(); + } + catch (Exception ex) + { + Logger.Warning("PMCQ errored on cleanup"); + Logger.Exception(ex); + } + } + + private bool Stop() + { + if (!IsCanceled) + _tokenSrc.Cancel(); + + if (!IsCompleted) + _movesToReview.CompleteAdding(); + + _movesToReview.Clear(); + return _task.Wait(MAX_TIMEOUT); + } + + public void EnqueueMove(Position target) + { + try + { + if (IsCompleted) + return; + + _movesToReview.Add(target, _ct); + } + catch (Exception ex) + { + if (ex is OperationCanceledException || ex is ObjectDisposedException) + { + return; + } + Logger.Exception(ex); + } + } + + private void ProcessQueue() + { + try + { + foreach (var pos in _movesToReview.GetConsumingEnumerable(_ct)) + { + if (_moveChecker.IsUpdateValid(Prev, pos)) + { + Prev = pos; + } + else + { + _movesToReview.Clear(); + _player.CurrentPosition = Prev; + _player.SendForceUpdate(); + } + } + } + catch (Exception ex) + { + if (ex is OperationCanceledException || ex is ObjectDisposedException) + { + return; + } + Logger.Exception(ex); + } + } + + #region DISPOSAL + protected override void Dispose(bool disposing) + { + if (!disposing) + return; + + try + { + _movesToReview.Dispose(); + } + catch (Exception e) + { + Logger.Exception(e); + } + } + #endregion + } + + public class PlayerMoveChecker + { + private readonly Player _player; + private readonly AStarLimited _aStar; + private const int MAX_DIST = 10; + + public PlayerMoveChecker(Player player) + { + _player = player; + _aStar = new AStarLimited(Heuristic.Manhattan, _player.IsWalkable, MAX_DIST); + } + + public bool IsUpdateValid(Position prev, Position pos) + { + var dx = Math.Abs(prev.intX - pos.intX); + var dy = Math.Abs(prev.intY - pos.intY); + if (dx < 2 && dy < 2) + { + return true; + } + else if (dx > MAX_DIST || dy > MAX_DIST) + { + return false; + } + else if (_player.Zone.CheckLinearPath(prev, pos, _player.Slope)) + { + return true; + } + else if (_aStar.HasPath(prev.ToPoint(), pos.ToPoint())) + { + return true; + } + return false; + } + } +} diff --git a/src/Perpetuum/Players/PlayerMovement.cs b/src/Perpetuum/Players/PlayerMovement.cs new file mode 100644 index 000000000..75e5d8bbd --- /dev/null +++ b/src/Perpetuum/Players/PlayerMovement.cs @@ -0,0 +1,63 @@ +using System; + +namespace Perpetuum.Players +{ + public class PlayerMovement + { + private static readonly TimeSpan minStepTime = TimeSpan.FromMilliseconds(50); + private static readonly TimeSpan maxElapsedTime = TimeSpan.FromSeconds(1); + private readonly Player player; + + public PlayerMovement(Player player) + { + this.player = player; + } + + public void Update(TimeSpan elapsed) + { + var speed = player.Speed; + + if (speed <= 0.0) + { + return; + } + + elapsed = elapsed.Min(maxElapsedTime); + + var angle = player.Direction * MathHelper.PI2; + var vx = Math.Sin(angle) * speed; + var vy = Math.Cos(angle) * speed; + var px = player.CurrentPosition.X; + var py = player.CurrentPosition.Y; + + while (elapsed > TimeSpan.Zero) + { + var time = minStepTime; + + if (elapsed < minStepTime) + { + time = elapsed; + } + + elapsed -= minStepTime; + + var nx = px + (vx * time.TotalSeconds); + var ny = py - (vy * time.TotalSeconds); + + var dx = (int)px - (int)nx; + var dy = (int)py - (int)ny; + + if ((dx != 0 || dy != 0) && + !player.IsWalkable((int)nx, (int)ny)) + { + break; + } + + px = nx; + py = ny; + } + + player.TryMove(new Position(px, py)); + } + } +} diff --git a/src/Perpetuum/PointExtensions.cs b/src/Perpetuum/PointExtensions.cs index f7ed2aead..e86a146ca 100644 --- a/src/Perpetuum/PointExtensions.cs +++ b/src/Perpetuum/PointExtensions.cs @@ -10,7 +10,6 @@ public static class PointExtensions private static readonly int[,] _neighbours = { { -1, -1 }, { 0, -1 }, { 1, -1 }, { -1, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 } }; private static readonly int[,] _nonDiagonalNeighbours = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } }; - public static Position ToPosition(this Point p) { return new Position(p.X + 0.5, p.Y + 0.5); diff --git a/src/Perpetuum/Robots/Robot.Locking.cs b/src/Perpetuum/Robots/Robot.Locking.cs index 153a39496..662bf7632 100644 --- a/src/Perpetuum/Robots/Robot.Locking.cs +++ b/src/Perpetuum/Robots/Robot.Locking.cs @@ -35,16 +35,32 @@ public UnitLock GetLockByUnit(Unit unit) return _lockHandler.GetLockByUnit(unit); } + [CanBeNull] + public TerrainLock GetLockByPosition(Position position) + { + return _lockHandler.GetLockByPosition(position); + } + public Lock GetPrimaryLock() { return _lockHandler.GetPrimaryLock(); } + public IEnumerable GetSecondaryLocks() + { + return _lockHandler.GetSecondaryLocks(); + } + public bool IsInLockingRange(Unit unit) { return _lockHandler.IsInLockingRange(unit); } + public bool IsInLockingRange(Position position) + { + return _lockHandler.IsInLockingRange(position); + } + public void ResetLocks() { _lockHandler.ResetLocks(); @@ -70,6 +86,11 @@ public void AddLock(Unit target, bool isPrimary) _lockHandler.AddLock(target, isPrimary); } + public void AddLock(Position target, bool isPrimary) + { + _lockHandler.AddLock(target, isPrimary); + } + public void AddLock(Lock newLock) { _lockHandler.AddLock(newLock); diff --git a/src/Perpetuum/Robots/Robot.Properties.cs b/src/Perpetuum/Robots/Robot.Properties.cs index 1ee5e95f0..c6eb91f31 100644 --- a/src/Perpetuum/Robots/Robot.Properties.cs +++ b/src/Perpetuum/Robots/Robot.Properties.cs @@ -11,87 +11,100 @@ namespace Perpetuum.Robots { partial class Robot { - private UnitOptionalProperty _decay; - private UnitOptionalProperty _tint; - - private ItemProperty _powerGridMax; - private ItemProperty _powerGrid; - private ItemProperty _cpuMax; - private ItemProperty _cpu; - private ItemProperty _ammoReloadTime; - private ItemProperty _missileHitChance; - private ItemProperty _decayChance; + private UnitOptionalProperty decay; + private UnitOptionalProperty tint; + + private ItemProperty powerGridMax; + private ItemProperty powerGrid; + private ItemProperty cpuMax; + private ItemProperty cpu; + private ItemProperty ammoReloadTime; + private ItemProperty missileHitChance; + private ItemProperty decayChance; + private ItemProperty mineDetectionRange; private void InitProperties() { - _decay = new UnitOptionalProperty(this, UnitDataType.Decay, k.decay, () => 255); - OptionalProperties.Add(_decay); + decay = new UnitOptionalProperty(this, UnitDataType.Decay, k.decay, () => 255); + OptionalProperties.Add(decay); + + tint = new UnitOptionalProperty(this,UnitDataType.Tint,k.tint,() => ED.Config.Tint); + OptionalProperties.Add(tint); - _tint = new UnitOptionalProperty(this,UnitDataType.Tint,k.tint,() => ED.Config.Tint); - OptionalProperties.Add(_tint); + powerGridMax = new UnitProperty(this, AggregateField.powergrid_max, AggregateField.powergrid_max_modifier); + AddProperty(powerGridMax); - _powerGridMax = new UnitProperty(this, AggregateField.powergrid_max, AggregateField.powergrid_max_modifier); - AddProperty(_powerGridMax); + powerGrid = new PowerGridProperty(this); + AddProperty(powerGrid); - _powerGrid = new PowerGridProperty(this); - AddProperty(_powerGrid); + cpuMax = new UnitProperty(this, AggregateField.cpu_max, AggregateField.cpu_max_modifier); + AddProperty(cpuMax); - _cpuMax = new UnitProperty(this, AggregateField.cpu_max, AggregateField.cpu_max_modifier); - AddProperty(_cpuMax); + cpu = new CpuProperty(this); + AddProperty(cpu); - _cpu = new CpuProperty(this); - AddProperty(_cpu); + ammoReloadTime = new UnitProperty(this, AggregateField.ammo_reload_time, AggregateField.ammo_reload_time_modifier); + AddProperty(ammoReloadTime); - _ammoReloadTime = new UnitProperty(this, AggregateField.ammo_reload_time, AggregateField.ammo_reload_time_modifier); - AddProperty(_ammoReloadTime); + missileHitChance = new UnitProperty(this, AggregateField.missile_miss, AggregateField.missile_miss_modifier); + AddProperty(missileHitChance); - _missileHitChance = new UnitProperty(this, AggregateField.missile_miss, AggregateField.missile_miss_modifier); - AddProperty(_missileHitChance); + decayChance = new DecayChanceProperty(this); + AddProperty(decayChance); - _decayChance = new DecayChanceProperty(this); - AddProperty(_decayChance); + mineDetectionRange = new UnitProperty( + this, + AggregateField.mine_detection_range, + AggregateField.undefined, + AggregateField.effect_mine_detection_range_modifier); + AddProperty(mineDetectionRange); } private double PowerGridMax { - get { return _powerGridMax.Value; } + get { return powerGridMax.Value; } } public double PowerGrid { - get { return _powerGrid.Value; } + get { return powerGrid.Value; } } private double CpuMax { - get { return _cpuMax.Value; } + get { return cpuMax.Value; } } public double Cpu { - get { return _cpu.Value; } + get { return cpu.Value; } } public TimeSpan AmmoReloadTime { - get { return TimeSpan.FromMilliseconds(_ammoReloadTime.Value); } + get { return TimeSpan.FromMilliseconds(ammoReloadTime.Value); } } public double MissileHitChance { - get { return _missileHitChance.Value; } + get { return missileHitChance.Value; } } public int Decay { - private get { return _decay.Value; } - set { _decay.Value = value & 255; } + private get { return decay.Value; } + set { decay.Value = value & 255; } } public Color Tint { - get { return _tint.Value; } - set { _tint.Value = value; } + get { return tint.Value; } + set { tint.Value = value; } + } + + public double MineDetectionRange + { + get { return mineDetectionRange.Value; } } public override void UpdateRelatedProperties(AggregateField field) @@ -133,14 +146,29 @@ public override ItemPropertyModifier GetPropertyModifier(AggregateField field) return modifier; } - public bool CheckPowerGridForModule(Module module) + public bool CheckPowerGridForModule(Module module, bool removing=false) { - return TotalPowerGridUsage + module.PowerGridUsage <= PowerGridMax; + return SimulateFitting(module, removing, PowerGridMax, TotalPowerGridUsage, AggregateField.powergrid_usage, AggregateField.powergrid_max_modifier); } - public bool CheckCpuForModule(Module module) + public bool CheckCpuForModule(Module module, bool removing = false) { - return TotalCpuUsage + module.CpuUsage <= CpuMax; + return SimulateFitting(module, removing, CpuMax, TotalCpuUsage, AggregateField.cpu_usage, AggregateField.cpu_max_modifier); + } + + private bool SimulateFitting(Module module, bool removing, double max, double current, AggregateField usageField, AggregateField maxModField) + { + double moduleUsageEstimate = 0; + var itemMod = module.BasePropertyModifiers.GetPropertyModifier(usageField); + module.SimulateRobotPropertyModifiers(this, ref itemMod); + itemMod.Modify(ref moduleUsageEstimate); + moduleUsageEstimate = removing ? -moduleUsageEstimate : moduleUsageEstimate; + if (removing && module.BasePropertyModifiers.GetPropertyModifier(maxModField).HasValue) + { + var mod = module.BasePropertyModifiers.GetPropertyModifier(maxModField).Value; + max /= Math.Max(mod, 1); + } + return current + moduleUsageEstimate <= max; } public double TotalPowerGridUsage @@ -159,7 +187,7 @@ public DecayChanceProperty(Unit owner) : base(owner, AggregateField.decay_chance protected override double CalculateValue() { - var v = 10 / owner.SignatureRadius * 0.01; + var v = 20 / owner.SignatureRadius * 0.01; return v; } } diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 6b839eb14..7d836390d 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Perpetuum.Accounting.Characters; using Perpetuum.Builders; -using Perpetuum.Common; using Perpetuum.Containers; using Perpetuum.Containers.SystemContainers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; - using Perpetuum.Items; using Perpetuum.Items.Templates; using Perpetuum.Modules; @@ -19,68 +14,107 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using System; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Robots { - - public class RobotInfoPacketBuilder : IBuilder + public abstract partial class Robot : Unit { - private readonly Robot _robot; - private readonly ZoneCommand _command; + private Lazy> _modules; + private Lazy> _activeModules; + private Lazy> _components; + private Lazy> _robotComponents; - public RobotInfoPacketBuilder(Robot robot,ZoneCommand command = ZoneCommand.RobotInfo) - { - _robot = robot; - _command = command; - } + public RobotHelper RobotHelper { protected get; set; } + public InsuranceHelper InsuranceHelper { protected get; set; } - public Packet Build() + public RobotTemplate Template { get; set; } + + public bool IsSelected => RobotHelper.IsSelected(this); + + public override double Health => IsRepackaged + ? base.Health + : ArmorMax > 0.0 && Armor > 0.0 + ? Armor.Ratio(ArmorMax) * 100 + : base.Health; + + public override double Mass => RobotComponents.Sum(c => c.Mass); + + public bool HasModule => Modules.Any(); + + public bool IsItemsInContainer { - var infoPacket = new Packet(_command); - infoPacket.AppendLong(_robot.Eid); - AppendRobotProperties(infoPacket); - return infoPacket; + get + { + if (IsRepackaged) + { + return false; + } + + RobotInventory robotInventory = GetContainer(); + + return robotInventory != null && robotInventory.HasChildren; + } } - private void AppendRobotProperties(Packet infoPacket) - { - var properties = _robot.Properties.Where(p => p.HasValue).ToArray(); + public override bool IsStackable => base.IsStackable && IsRepackaged; + + public IEnumerable ExtensionBonusEnablerExtensions => ExtensionBonuses.Select(cb => new Extension(cb.extensionId, 1)); + + protected IEnumerable ExtensionBonuses => RobotComponents.SelectMany(component => component.ExtensionBonuses); + + public IEnumerable Modules => _modules.Value; - infoPacket.AppendInt(properties.Length); + public IEnumerable ActiveModules => _activeModules.Value; - foreach (var property in properties) + public IEnumerable Components => _components.Value; + + public IEnumerable RobotComponents => _robotComponents.Value; + + public override double Volume + { + get { - infoPacket.AppendProperty(property); + if (IsRepackaged) + { + return base.Volume; + } + + double volume = RobotComponents.Sum(c => c.Volume); + + volume *= Quantity; + + return volume; } } - } - public abstract partial class Robot : Unit - { + public bool IsTrashed => Trashcan.IsItemTrashed(this); + protected Robot() { InitLockHander(); InitProperties(); } - public RobotHelper RobotHelper { protected get; set; } - public InsuranceHelper InsuranceHelper { protected get; set; } - - public RobotTemplate Template { get; set; } - - protected virtual void OnLockError(Lock @lock, ErrorCodes error) + public override void Initialize() { + InitComponents(); + base.Initialize(); } public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } public void VisitModules(IEntityVisitor visitor) { - foreach (var module in Modules) + foreach (Module module in Modules) { module.AcceptVisitor(visitor); } @@ -88,7 +122,7 @@ public void VisitModules(IEntityVisitor visitor) public void VisitRobotComponents(IEntityVisitor visitor) { - foreach (var component in RobotComponents) + foreach (RobotComponent component in RobotComponents) { component.AcceptVisitor(visitor); } @@ -96,80 +130,37 @@ public void VisitRobotComponents(IEntityVisitor visitor) public void VisitRobotInventory(IEntityVisitor visitor) { - var container = GetContainer(); + RobotInventory container = GetContainer(); + container?.AcceptVisitor(visitor); } public override IDictionary GetDebugInfo() { - var info = base.GetDebugInfo(); - info.Add("locksCount", _lockHandler.Count); - return info; - } - - public bool IsSelected => RobotHelper.IsSelected(this); - - public override double Health - { - get - { - if (IsRepackaged) - { - return base.Health; - } - - return Armor.Ratio(ArmorMax) * 100; - } - } - - public override double Mass - { - get { return RobotComponents.Sum(c => c.Mass); } - } - - protected virtual void OnLockStateChanged(Lock @lock) - { - States.LockSomething = _lockHandler.Count > 0; - - var unitLock = @lock as UnitLock; - if (unitLock != null) - { - UpdateTypes |= UnitUpdateTypes.Lock; - UpdateVisibilityOf(unitLock.Target); - } + IDictionary info = base.GetDebugInfo(); - var builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); - OnBroadcastPacket(builder.ToProxy()); - } - - protected override void OnUpdate(TimeSpan time) - { - base.OnUpdate(time); - - _lockHandler.Update(time); + info.Add("locksCount", _lockHandler.Count); - foreach (var robotComponent in RobotComponents) - { - robotComponent.Update(time); - } + return info; } public void FullArmorRepair() { - DynamicProperties.Update(k.armor,1.0); + DynamicProperties.Update(k.armor, 1.0); Armor = ArmorMax; } public void FullCoreRecharge() { - var currentCore = DynamicProperties.GetOrAdd(k.currentCore); - - var coreMaxValue = CoreMax; + double currentCore = DynamicProperties.GetOrAdd(k.currentCore); + double coreMaxValue = CoreMax; if (currentCore >= coreMaxValue) + { return; + } - DynamicProperties.Update(k.currentCore,coreMaxValue); + DynamicProperties.Update(k.currentCore, coreMaxValue); Core = CoreMax; } @@ -179,111 +170,57 @@ public RobotInventory GetContainer() return Children.OfType().FirstOrDefault(); } - protected internal override double ComputeHeight() - { - return RobotComponents.Sum(c => c.ComputeHeight()); - } - - public bool HasModule - { - get { return Modules.Any(); } - } - - public bool IsItemsInContainer - { - get - { - if (IsRepackaged) - return false; - - var robotInventory = GetContainer(); - if (robotInventory == null) - return false; - - return robotInventory.HasChildren; - } - } - public void StopAllModules() { - foreach (var module in ActiveModules) + foreach (ActiveModule module in ActiveModules) { module.State.SwitchTo(ModuleStateType.Idle); } } - protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) - { - base.OnDamageTaken(source, e); - - var decayChance = _decayChance.Value; - var random = FastRandom.NextDouble(); - - if (decayChance >= random) - { - var d = Decay; - if (d > 0) - { - d--; - Decay = d; - } - } - } - public override void OnDeleteFromDb() { InsuranceHelper.DeleteAndInform(this); base.OnDeleteFromDb(); } - public void EmptyRobot(Character character,Container targetContainer,bool withContainer = true) + public void EmptyRobot(Character character, Container targetContainer, bool withContainer = true) { - foreach (var module in Modules) + foreach (Module module in Modules) { module.Unequip(targetContainer); } if (!withContainer) + { return; + } - var container = GetContainer(); - container?.RelocateItems(character, character, container.GetItems(), targetContainer); - } + RobotInventory container = GetContainer(); - public override bool IsStackable - { - get { return base.IsStackable && IsRepackaged; } + container?.RelocateItems(character, character, container.GetItems(), targetContainer); } public override Dictionary ToDictionary() { - var dictionary = base.ToDictionary(); + Dictionary dictionary = base.ToDictionary(); - foreach (var component in RobotComponents) + foreach (RobotComponent component in RobotComponents) { dictionary.Add(component.ComponentName, component.ToDictionary()); } - var container = GetContainer(); + RobotInventory container = GetContainer(); - if ( container != null ) + if (container != null) { - dictionary.Add(k.container,container.ToDictionary()); + dictionary.Add(k.container, container.ToDictionary()); } dictionary.Add(k.decay, Decay); dictionary.Add(k.tint, Tint); - return dictionary; - } - - public IEnumerable ExtensionBonusEnablerExtensions - { - get { return ExtensionBonuses.Select(cb => new Extension(cb.extensionId, 1)); } - } - protected IEnumerable ExtensionBonuses - { - get { return RobotComponents.SelectMany(component => component.ExtensionBonuses); } + return dictionary; } [CanBeNull] @@ -316,73 +253,125 @@ public RobotComponent GetRobotComponent(RobotComponentType componentType) return RobotComponents.FirstOrDefault(c => c.Type == componentType); } - public IEnumerable ActiveModules + public void CheckEnergySystemAndThrowIfFailed(Module module, bool isRemoving = false) { - get { return Modules.OfType(); } - } - - public IEnumerable Modules - { - get { return RobotComponents.SelectMany(c => c.Modules); } - } - - public IEnumerable Components - { - get { return Children.OfType(); } - } + if (!CheckPowerGridForModule(module, isRemoving)) + { + throw PerpetuumException.Create(ErrorCodes.OutOfPowergrid); + } - public IEnumerable RobotComponents - { - get { return Components.OfType(); } + if (!CheckCpuForModule(module, isRemoving)) + { + throw PerpetuumException.Create(ErrorCodes.OutOfCpu); + } } public void CheckEnergySystemAndThrowIfFailed() { if (PowerGrid < 0) + { throw PerpetuumException.Create(ErrorCodes.OutOfPowergrid).SetData("powerGrid", Math.Abs(PowerGridMax - PowerGrid)).SetData("powerGridMax", PowerGridMax); + } if (Cpu < 0) + { throw PerpetuumException.Create(ErrorCodes.OutOfCpu).SetData("cpu", Math.Abs(CpuMax - Cpu)).SetData("cpuMax", CpuMax); + } } public void CreateComponents() { - foreach (var component in Template.BuildComponents()) + foreach (Item component in Template.BuildComponents()) { AddChild(component); } } - protected override bool IsDetected(Unit target) + [CanBeNull] + public T GetRobotComponent() where T : RobotComponent { - if (_lockHandler.IsLocked(target)) - return true; + return RobotComponents.OfType().FirstOrDefault(); + } - return base.IsDetected(target); + protected virtual void OnLockError(Lock @lock, ErrorCodes error) + { } - public override double Volume + protected virtual void OnLockStateChanged(Lock @lock) { - get + States.LockSomething = _lockHandler.Count > 0; + + + if (@lock is UnitLock unitLock) { - if (IsRepackaged) - return base.Volume; + UpdateTypes |= UnitUpdateTypes.Lock; + UpdateVisibilityOf(unitLock.Target); + } - var volume = RobotComponents.Sum(c => c.Volume); - volume *= Quantity; - return volume; + AnonymousBuilder builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); + + OnBroadcastPacket(builder.ToProxy()); + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + + _lockHandler.Update(time); + + foreach (RobotComponent robotComponent in RobotComponents) + { + robotComponent.Update(time); } } - public bool IsTrashed + protected internal override double ComputeHeight() { - get { return Trashcan.IsItemTrashed(this); } + return RobotComponents.Sum(c => c.ComputeHeight()); } - [CanBeNull] - public T GetRobotComponent() where T : RobotComponent + protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) { - return RobotComponents.OfType().FirstOrDefault(); + base.OnDamageTaken(source, e); + + double decayChance = this.decayChance.Value; + double random = FastRandom.NextDouble(); + + if (decayChance >= random) + { + int d = Decay; + + if (d > 0) + { + d--; + Decay = d; + } + } + } + + protected override bool IsDetected(Unit target) + { + return _lockHandler.IsLocked(target) || base.IsDetected(target); + } + + protected override void OnBeforeRemovedFromZone(IZone zone) + { + Module remoteController = Modules?.FirstOrDefault(x => x is RemoteControllerModule); + + if (remoteController != null) + { + (remoteController as RemoteControllerModule).CloseAllChannels(); + } + + base.OnBeforeRemovedFromZone(zone); + } + + private void InitComponents() + { + _components = new Lazy>(() => Children.OfType().ToArray()); + _robotComponents = new Lazy>(() => Components.OfType().ToArray()); + _modules = new Lazy>(() => RobotComponents.SelectMany(c => c.Modules).ToArray()); + _activeModules = new Lazy>(() => Modules.OfType().ToArray()); } } } diff --git a/src/Perpetuum/Robots/RobotComponent.cs b/src/Perpetuum/Robots/RobotComponent.cs index 5cacbcba1..b68ba5480 100644 --- a/src/Perpetuum/Robots/RobotComponent.cs +++ b/src/Perpetuum/Robots/RobotComponent.cs @@ -42,6 +42,20 @@ protected RobotComponent(RobotComponentType type,IExtensionReader extensionReade _extensionReader = extensionReader; } + public override void Initialize() + { + InitModules(); + base.Initialize(); + } + + private Lazy> _modules; + private Lazy> _activeModules; + private void InitModules() + { + _modules = new Lazy>(() => Children.OfType().ToArray()); + _activeModules = new Lazy>(() => Modules.OfType().ToArray()); + } + public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) @@ -76,12 +90,12 @@ public string ComponentName public IEnumerable Modules { - get { return Children.OfType(); } + get { return _modules.Value; } } public IEnumerable ActiveModules { - get { return Modules.OfType(); } + get { return _activeModules.Value; } } public void Update(TimeSpan time) @@ -233,8 +247,9 @@ public override Dictionary ToDictionary() public override ItemPropertyModifier GetPropertyModifier(AggregateField field) { var modifier = base.GetPropertyModifier(field); + var modifyingModules = Modules.Where(m => !m.Properties.Any(p => p.Field == field)); - foreach (var module in Modules.Where(m => m.IsPassive)) + foreach (var module in modifyingModules) { var m = module.GetBasePropertyModifier(field); m.Modify(ref modifier); diff --git a/src/Perpetuum/Robots/RobotHelper.cs b/src/Perpetuum/Robots/RobotHelper.cs index 16adbb944..67de3dc8f 100644 --- a/src/Perpetuum/Robots/RobotHelper.cs +++ b/src/Perpetuum/Robots/RobotHelper.cs @@ -47,12 +47,16 @@ public Robot LoadRobotForCharacter(long robotEid, Character character, bool chec public Robot LoadRobot(long robotEid) { - return (Robot) _unitHelper.LoadUnit(robotEid); + var robot = (Robot) _unitHelper.LoadUnit(robotEid); + robot?.Initialize(); + return robot; } public Robot LoadRobotOrThrow(long robotEid) { - return (Robot) _unitHelper.LoadUnitOrThrow(robotEid); + var robot = (Robot) _unitHelper.LoadUnitOrThrow(robotEid); + robot.Initialize(); + return robot; } public bool IsSelected(Robot robot) diff --git a/src/Perpetuum/Robots/RobotInfoPacketBuilder.cs b/src/Perpetuum/Robots/RobotInfoPacketBuilder.cs new file mode 100644 index 000000000..f93a596b4 --- /dev/null +++ b/src/Perpetuum/Robots/RobotInfoPacketBuilder.cs @@ -0,0 +1,40 @@ +using Perpetuum.Builders; +using Perpetuum.Common; +using Perpetuum.Zones; +using System.Linq; + +namespace Perpetuum.Robots +{ + public class RobotInfoPacketBuilder : IBuilder + { + private readonly Robot _robot; + private readonly ZoneCommand _command; + + public RobotInfoPacketBuilder(Robot robot, ZoneCommand command = ZoneCommand.RobotInfo) + { + _robot = robot; + _command = command; + } + + public Packet Build() + { + var infoPacket = new Packet(_command); + infoPacket.AppendLong(_robot.Eid); + AppendRobotProperties(infoPacket); + + return infoPacket; + } + + private void AppendRobotProperties(Packet infoPacket) + { + var properties = _robot.Properties.Where(p => p.HasValue).ToArray(); + + infoPacket.AppendInt(properties.Length); + + foreach (var property in properties) + { + infoPacket.AppendProperty(property); + } + } + } +} diff --git a/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs b/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs new file mode 100644 index 000000000..746c78b2b --- /dev/null +++ b/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs @@ -0,0 +1,21 @@ +using Perpetuum.Data; +using Perpetuum.Log; +using Perpetuum.Threading.Process; +using System; + +namespace Perpetuum.Services +{ + public class ArtifactRefresher : Process + { + public override void Update(TimeSpan time) + { + DoRefresh(); + } + + public void DoRefresh() + { + Logger.Info("Artifact Refresh"); + Db.Query().CommandText("EXEC [opp].[artifactRefresh];").ExecuteNonQuery(); + } + } +} diff --git a/src/Perpetuum/Services/Channels/Channel.cs b/src/Perpetuum/Services/Channels/Channel.cs index 64269c297..ce9bac006 100644 --- a/src/Perpetuum/Services/Channels/Channel.cs +++ b/src/Perpetuum/Services/Channels/Channel.cs @@ -6,29 +6,38 @@ namespace Perpetuum.Services.Channels { public class Channel { - private Dictionary _members = new Dictionary(); + private Dictionary _members = new Dictionary(); public int Id { get; private set; } - public ChannelType Type { get; private set; } public string Name { get; private set; } public string Topic { get; private set; } public string Password { get; private set; } - public IChannelLogger Logger { get; private set; } + private ChannelType _type; + private ChannelType _prevType; + public ChannelType Type + { + get => _type; + private set + { + _prevType = _type; + _type = value; + } + } private Channel() { - + } - public Channel(int id,ChannelType type,string name,string topic,string password,IChannelLogger logger) : this(type,name,logger) + public Channel(int id, ChannelType type, string name, string topic, string password, IChannelLogger logger) : this(type, name, logger) { Id = id; Topic = topic; Password = password; } - public Channel(ChannelType type, string name,IChannelLogger logger) + public Channel(ChannelType type, string name, IChannelLogger logger) { Type = type; Name = name; @@ -60,7 +69,7 @@ public Channel SetId(int id) public Channel SetTopic(string topic) { - if ( !string.IsNullOrEmpty(topic) && topic.Length > 200) + if (!string.IsNullOrEmpty(topic) && topic.Length > 200) topic = topic.Substring(0, 199); if (topic == Topic) @@ -100,7 +109,7 @@ public Channel SetMember(ChannelMember member) if (member == null) return this; - var members = new Dictionary(_members) {[member.character] = member}; + var members = new Dictionary(_members) { [member.character] = member }; return new Channel { @@ -114,6 +123,19 @@ public Channel SetMember(ChannelMember member) }; } + public void SetAdmin(bool isadminchannel) + { + if (isadminchannel) + { + Type = ChannelType.Admin; + } + else + { + Type = _prevType; + } + } + + public Channel RemoveMember(Character member) { var members = new Dictionary(_members); @@ -149,7 +171,7 @@ public void CheckPasswordAndThrowIfMismatch(string password) public void CheckRoleAndThrowIfFailed(Character member, ChannelMemberRole role) { - if (member == Character.None) + if (member == Character.None || Character.IsSystemCharacter(member)) return; GetMember(member).ThrowIfNull(ErrorCodes.NotMemberOfChannel).HasRole(role).ThrowIfFalse(ErrorCodes.InsufficientPrivileges); @@ -198,7 +220,7 @@ public bool IsConstant { switch (Type) { - case ChannelType.Public: + case ChannelType.Public | ChannelType.Admin: return false; } diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index 985002f12..5b349a017 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -5,6 +5,8 @@ using System.Threading; using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Channels.ChatCommands; using Perpetuum.Services.Sessions; namespace Perpetuum.Services.Channels @@ -17,8 +19,9 @@ public class ChannelManager : IChannelManager private readonly IChannelBanRepository _banRepository; private readonly ChannelLoggerFactory _channelLoggerFactory; private readonly ConcurrentDictionary _channels = new ConcurrentDictionary(); + private readonly AdminCommandRouter _adminCommand; - public ChannelManager(ISessionManager sessionManager,IChannelRepository channelRepository,IChannelMemberRepository memberRepository,IChannelBanRepository banRepository,ChannelLoggerFactory channelLoggerFactory) + public ChannelManager(ISessionManager sessionManager,IChannelRepository channelRepository,IChannelMemberRepository memberRepository,IChannelBanRepository banRepository,ChannelLoggerFactory channelLoggerFactory, AdminCommandRouter adminCommand) { _sessionManager = sessionManager; _sessionManager.SessionAdded += OnSessionAdded; @@ -27,6 +30,7 @@ public ChannelManager(ISessionManager sessionManager,IChannelRepository channelR _memberRepository = memberRepository; _banRepository = banRepository; _channelLoggerFactory = channelLoggerFactory; + _adminCommand = adminCommand; foreach (var channel in channelRepository.GetAll()) { @@ -50,6 +54,11 @@ private void SessionOnCharacterSelected(ISession session, Character character) var channel = UpdateChannel(channelName, c => c.SetMember(member)); channel?.SendMemberOnlineStateToAll(_sessionManager, member, true); } + + if (character.IsDocked) + { + character.GetCurrentDockingBase()?.TryJoinChannel(character); + } } private void SessionOnCharacterDeselected(ISession session, Character character) @@ -249,7 +258,7 @@ public void SetMemberRole(string channelName, Character issuer,Character charact channel.SendToAll(_sessionManager, n); } - public void Talk(string channelName, Character sender, string message) + public void Talk(string channelName, Character sender, string message, IRequest request) { Channel channel; if ( !_channels.TryGetValue(channelName,out channel)) @@ -262,7 +271,28 @@ public void Talk(string channelName, Character sender, string message) channel.Logger.LogMessage(sender, message); m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); - channel.SendMessageToAll(_sessionManager,sender,message); + + //check if it's an admin command and don't display it until later in _adminCommand + if (_adminCommand.IsAdminCommand(message)) + { + _adminCommand.TryParseAdminCommand(sender, message, request, channel, this); + } + else + { + channel.SendMessageToAll(_sessionManager, sender, message); + } + + } + + public void Announcement(string channelName, Character sender, string message) + { + Channel channel; + if (!_channels.TryGetValue(channelName, out channel)) + return; + + channel.Logger.LogMessage(sender, message); + + channel.SendMessageToAll(_sessionManager, sender, message); } public void KickOrBan(string channelName, Character issuer, Character character, string message, bool ban) @@ -353,5 +383,10 @@ public IEnumerable GetPublicChannels() { return _channels.Values.Where(c => c.Type == ChannelType.Public || c.Type == ChannelType.Highlighted); } + + public IEnumerable GetAllChannels() + { + return _channels.Values; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Channels/ChannelMessageHandler.cs b/src/Perpetuum/Services/Channels/ChannelMessageHandler.cs new file mode 100644 index 000000000..76be35347 --- /dev/null +++ b/src/Perpetuum/Services/Channels/ChannelMessageHandler.cs @@ -0,0 +1,44 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Data; +using System.Linq; + +namespace Perpetuum.Services.Channels +{ + public static class ChannelMessageHandler + { + // TODO hardcoded character and channel names + private const string WELCOME_CHANNEL_NAME = "Recruitment"; + private const string HELP_CHANNEL_NAME = "regchannel_help"; + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; + + public static void SendNewPlayerTutorialMessage(IChannelManager channelManager, string newCharacterName) + { + var test = channelManager.Channels.All(channel => channel.Name.ToLower().Contains("help")); + + SendMessage(channelManager, HELP_CHANNEL_NAME, PreMadeChatMessageNames.TUTORIAL_ARRIVE, newCharacterName); + } + + public static void SendWelcomeMessageExitTutorial(IChannelManager channelManager, string newCharacterName) + { + SendMessage(channelManager, WELCOME_CHANNEL_NAME, PreMadeChatMessageNames.TUTORIAL_FINISH, newCharacterName); + } + + private static void SendMessage(IChannelManager channelManager, string channelName, string messageName, string newCharacterName) + { + string message; + using (var scope = Db.CreateTransaction()) + { + var records = Db.Query() + .CommandText("SELECT message FROM premadechatmessage WHERE name = @messageName") + .SetParameter("@messageName", messageName) + .Execute(); + message = records.First().GetValue("message"); + scope.Complete(); + } + message = message.Replace("$NAME$", newCharacterName); + + Character sender = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + channelManager.Announcement(channelName, sender, message); + } + } +} diff --git a/src/Perpetuum/Services/Channels/ChannelRepository.cs b/src/Perpetuum/Services/Channels/ChannelRepository.cs index 1d69b16d9..afcc54344 100644 --- a/src/Perpetuum/Services/Channels/ChannelRepository.cs +++ b/src/Perpetuum/Services/Channels/ChannelRepository.cs @@ -34,10 +34,11 @@ public void Delete(Channel channel) public void Update(Channel channel) { - Db.Query().CommandText("update channels set topic = @topic,password = @password where id = @channelid") + Db.Query().CommandText("update channels set topic = @topic, password = @password, type=@type where id = @channelid") .SetParameter("@topic", channel.Topic) .SetParameter("@password", channel.Password) - .SetParameter("@channelid",channel.Id) + .SetParameter("@type", channel.Type) + .SetParameter("@channelid",channel.Id) .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); } diff --git a/src/Perpetuum/Services/Channels/ChannelType.cs b/src/Perpetuum/Services/Channels/ChannelType.cs index 3722f5f38..42326648a 100644 --- a/src/Perpetuum/Services/Channels/ChannelType.cs +++ b/src/Perpetuum/Services/Channels/ChannelType.cs @@ -6,6 +6,7 @@ public enum ChannelType Highlighted, Corporation, Gang, - Station + Station, + Admin } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs new file mode 100644 index 000000000..03a44927d --- /dev/null +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs @@ -0,0 +1,43 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Sessions; +using System.Linq; + +namespace Perpetuum.Services.Channels.ChatCommands +{ + public class AdminCommandData + { + public class CommandArgs + { + public string Name { get; private set; } + public string[] Args { get; private set; } + public CommandArgs(string[] commandArray) + { + Name = commandArray[0].Substring(1).ToLower().Trim(); + Args = commandArray.Skip(1).Select(s => s.Trim()).ToArray(); + } + } + + public static AdminCommandData Create(Character sender, string[] command, IRequest request, Channel channel, IChannelManager channelManager, ISessionManager sessionManager, bool devMode) + { + return new AdminCommandData + { + Sender = sender, + Command = new CommandArgs(command), + Request = request, + Channel = channel, + ChannelManager = channelManager, + SessionManager = sessionManager, + IsDevMode = devMode + }; + } + + public Character Sender { get; private set; } + public CommandArgs Command { get; private set; } + public IRequest Request { get; private set; } + public Channel Channel { get; private set; } + public IChannelManager ChannelManager { get; private set; } + public ISessionManager SessionManager { get; private set; } + public bool IsDevMode { get; private set; } + } +} diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs new file mode 100644 index 000000000..6c4374e9f --- /dev/null +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -0,0 +1,1627 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.GenXY; +using Perpetuum.Groups.Corporations; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Services.Weather; +using Perpetuum.Zones; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Teleporting.Strategies; +using Perpetuum.Zones.Terrains; +using Perpetuum.Zones.Terrains.Materials.Plants; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Services.Channels.ChatCommands +{ + public class ChatCommand : Attribute + { + public string Command { get; private set; } + public ChatCommand(string command) + { + Command = command.ToLower(); + } + } + + + public static class AdminCommandHandlers + { + #region helpers + private static bool IsDevModeEnabled(AdminCommandData data) + { + return data.IsDevMode; + } + private static void SendMessageToAll(AdminCommandData data, string message) + { + data.Channel.SendMessageToAll(data.SessionManager, data.Sender, message); + } + private static void HandleLocalRequest(AdminCommandData data, string cmd) + { + data.Request.Session.HandleLocalRequest(data.Request.Session.CreateLocalRequest(cmd)); + } + private static void ZoneSetTilesControl(AdminCommandData data, TerrainControlFlags flag) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = bool.TryParse(data.Command.Args[0], out bool adddelete); + _ = bool.TryParse(data.Command.Args[1], out bool keeplock); + + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + + List lockedtiles = player.GetLocks(); + + using (new TerrainUpdateMonitor(zone)) + { + foreach (Lock item in lockedtiles) + { + Position pos = (item as TerrainLock).Location; + TerrainControlInfo ti = zone.Terrain.Controls.GetValue(pos); + ti.SetFlags(flag, adddelete); + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + SendMessageToAll(data, string.Format("Altered state of control layer on {0} Tiles ({1}: set to {2})", lockedtiles.Count, flag, adddelete)); + } + private static void LockOrUnlockZoneLayers(AdminCommandData data, bool toLock) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + bool err = false; + int zoneId = -1; + if (data.Command.Args.IsNullOrEmpty()) + { + Character character = data.Request.Session.Character; + zoneId = character.ZoneId ?? -1; + } + else if (data.Command.Args.Length >= 1) + { + err = !int.TryParse(data.Command.Args[0], out int id); + zoneId = id; + } + + if (err) + { + SendMessageToAll(data, "Error parsing args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + CheckZoneId(data, zoneId); + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + zone.IsLayerEditLocked = toLock; + SendMessageToAll(data, $"All layers on zone {zoneId} {(toLock ? "LOCKED" : "UNLOCKED")}!"); + } + public static void CheckZoneId(AdminCommandData data, int zoneId) + { + if (!data.Request.Session.ZoneMgr.ContainsZone(zoneId)) + { + SendMessageToAll(data, $"Bad 'zoneId' arg"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + } + + public static void CheckRequiredArgLength(AdminCommandData data, int expectedLength) + { + if (data.Command.Args.Length != expectedLength) + { + SendMessageToAll(data, $"Command expects {expectedLength} args, but was given {data.Command.Args.Length} args"); + throw PerpetuumException.Create(ErrorCodes.TooManyOrTooFewArguments); + } + } + public static void Unknown(AdminCommandData data) + { + SendMessageToAll(data, $"Unknown command: {data.Command.Name}"); + } + #endregion + #region AdminCommands + [ChatCommand("Secure")] + public static void Secure(AdminCommandData data) + { + data.Channel.SetAdmin(true); + data.Channel.SendMessageToAll(data.SessionManager, data.Sender, "Channel Secured."); + } + [ChatCommand("UnSecure")] + public static void UnSecure(AdminCommandData data) + { + data.Channel.SetAdmin(false); + data.Channel.SendMessageToAll(data.SessionManager, data.Sender, "Channel is now public."); + } + [ChatCommand("Shutdown")] + public static void Shutdown(AdminCommandData data) + { + DateTime shutdownin = DateTime.Now; + if (!int.TryParse(data.Command.Args[1], out int minutes)) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + shutdownin = shutdownin.AddMinutes(minutes); + + Dictionary dictionary = new Dictionary() + { + { "message", data.Command.Args[0] }, + { "date", shutdownin } + }; + + string cmd = string.Format("serverShutDown:relay:{0}", GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + + [ChatCommand("RelayClose")] + public static void RelayClosed(AdminCommandData data) + { + string cmd = string.Format("relayClose:relay:null"); + HandleLocalRequest(data, cmd); + data.Channel.SendMessageToAll(data.SessionManager, data.Sender, string.Format("Relay is Closed >> Only Admins can Login")); + } + + [ChatCommand("RelayOpen")] + public static void RelayOpen(AdminCommandData data) + { + string cmd = string.Format("relayOpen:relay:null"); + HandleLocalRequest(data, cmd); + data.Channel.SendMessageToAll(data.SessionManager, data.Sender, string.Format("Relay is Open >> All Players can Login")); + } + + [ChatCommand("ShutdownCancel")] + public static void ShutdownCancel(AdminCommandData data) + { + string cmd = string.Format("serverShutDownCancel:relay:null"); + HandleLocalRequest(data, cmd); + } + [ChatCommand("SetMaxSessions")] + public static void SetMaxSessions(AdminCommandData data) + { + int minSessionBound = 1; + int maxSessionBound = 10000; + CheckRequiredArgLength(data, 1); + if (!int.TryParse(data.Command.Args[0], out int maxPlayerSessions)) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + if (!maxPlayerSessions.IsInRange(minSessionBound, maxSessionBound)) + { + SendMessageToAll(data, $"maxPlayerSessions {maxPlayerSessions} is outside of accepted range [{minSessionBound},{maxSessionBound}]"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + Dictionary dictionary = new Dictionary() { { k.amount, maxPlayerSessions } }; + string cmd = string.Format("{0}:relay:{1}", Commands.SetMaxUserCount.Text, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("JumpTo")] + public static void JumpTo(AdminCommandData data) + { + int zoneId; + int x; + int y; + CheckRequiredArgLength(data, 3); + + try + { + zoneId = int.Parse(data.Command.Args[0]); + x = int.Parse(data.Command.Args[1]); + y = int.Parse(data.Command.Args[2]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + CheckZoneId(data, zoneId); + + Dictionary dictionary = new Dictionary() + { + { "zoneID" , zoneId }, + { "x" , x }, + { "y" , y } + }; + + string cmd = string.Format("jumpAnywhere:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("MovePlayer")] + public static void MovePlayer(AdminCommandData data) + { + int characterId; + int zoneId; + int x; + int y; + CheckRequiredArgLength(data, 4); + + try + { + characterId = int.Parse(data.Command.Args[0]); + zoneId = int.Parse(data.Command.Args[1]); + x = int.Parse(data.Command.Args[2]); + y = int.Parse(data.Command.Args[3]); + } + catch (Exception ex) + { + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + CheckZoneId(data, zoneId); + + // get the target character's session. + Sessions.ISession charactersession = data.SessionManager.GetByCharacter(characterId); + + if (charactersession.Character.ZoneId == null) + { + SendMessageToAll(data, string.Format("ERR: Character with ID {0} does not have a zone. Are they docked?", characterId)); + return; + } + + // get destination zone. + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + + if (charactersession.Character.ZoneId == null) + { + SendMessageToAll(data, string.Format("ERR: Invalid Zone ID {0}", zoneId)); + return; + } + + // get a teleporter object to teleport the player. + TeleportToAnotherZone tp = new TeleportToAnotherZone(zone); + + // we need the player (robot, etc) to teleport on the origin zone + Players.Player player = data.Request.Session.ZoneMgr.GetZone((int)charactersession.Character.ZoneId).GetPlayer(charactersession.Character.ActiveRobotEid); + //var player = zone.GetPlayer(charactersession.Character.Eid); + + // set the position. + tp.TargetPosition = new Position(x, y); + + // do it. + _ = tp.DoTeleportAsync(player); + SendMessageToAll(data, string.Format("Moved Character {0}-{1} to Zone {2} @ {3},{4}", characterId, charactersession.Character.Nick, zone.Id, x, y)); + } + [ChatCommand("GiveItem")] + public static void GiveItem(AdminCommandData data) + { + CheckRequiredArgLength(data, 2); + _ = int.TryParse(data.Command.Args[0], out int definition); + _ = int.TryParse(data.Command.Args[1], out int qty); + + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "quantity", qty } + }; + + string cmd = string.Format("createItem:relay:{0}", GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, string.Format("Gave Item {0} ", definition)); + } + [ChatCommand("GetLockedTileProperties")] + public static void GetLockedTileProperties(AdminCommandData data) + { + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + + Lock lockedtile = player.GetPrimaryLock(); + + TerrainControlInfo ti = zone.Terrain.Controls.GetValue((lockedtile as TerrainLock).Location); + + SendMessageToAll(data, string.Format("Tile at {0},{1} has the following flags..", (lockedtile as TerrainLock).Location.X, (lockedtile as TerrainLock).Location.Y)); + SendMessageToAll(data, "TerrainControlFlags:"); + foreach (TerrainControlFlags f in Enum.GetValues(typeof(TerrainControlFlags))) + { + if (ti.Flags.HasFlag(f) && f != TerrainControlFlags.Undefined) + { + SendMessageToAll(data, string.Format("{0}", f.ToString())); + } + } + + BlockingInfo bi = zone.Terrain.Blocks.GetValue((lockedtile as TerrainLock).Location); + + SendMessageToAll(data, "BlockingFlags:"); + foreach (BlockingFlags f in Enum.GetValues(typeof(BlockingFlags))) + { + if (bi.Flags.HasFlag(f) && f != BlockingFlags.Undefined) + { + SendMessageToAll(data, string.Format("{0}", f.ToString())); + } + } + + PlantInfo pi = zone.Terrain.Plants.GetValue((lockedtile as TerrainLock).Location); + + SendMessageToAll(data, "PlantType:"); + foreach (PlantType f in Enum.GetValues(typeof(PlantType))) + { + if (pi.type.HasFlag(f) && f != PlantType.NotDefined) + { + SendMessageToAll(data, string.Format("{0}", f.ToString())); + } + } + + SendMessageToAll(data, "GroundType:"); + foreach (GroundType f in Enum.GetValues(typeof(GroundType))) + { + if (pi.groundType.HasFlag(f)) + { + SendMessageToAll(data, string.Format("{0}", f.ToString())); + } + } + } + [ChatCommand("SetVisibility")] + public static void SetVisibility(AdminCommandData data) + { + CheckRequiredArgLength(data, 1); + _ = bool.TryParse(data.Command.Args[0], out bool visiblestate); + + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + + player.HasGMStealth = !visiblestate; + + SendMessageToAll(data, string.Format("Player {0} visibility is {1}", player.Character.Nick, visiblestate)); + } + [ChatCommand("ZoneDrawStatMap")] + public static void ZoneDrawStatMap(AdminCommandData data) + { + int zoneId = data.Sender.ZoneId ?? -1; + if (data.Command.Args.Length < 1) + { + SendMessageToAll(data, $"Missing 'type' arg"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + string type = data.Command.Args[0]; + if (data.Command.Args.Length > 1) + { + if (!int.TryParse(data.Command.Args[1], out zoneId)) + { + SendMessageToAll(data, $"Bad 'zoneId' arg"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + } + CheckZoneId(data, zoneId); + Dictionary dictionary = new Dictionary() { { k.type, type } }; + string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", zoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Draw map command accepted: {dictionary.ToDebugString()} \r\nDrawing... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Command complete!"); + } + [ChatCommand("ListAllPlayersInZone")] + public static void ListAllPlayersInZone(AdminCommandData data) + { + _ = int.TryParse(data.Command.Args[0], out int zoneid); + CheckZoneId(data, zoneid); + + SendMessageToAll(data, string.Format("Players On Zone {0}", zoneid)); + SendMessageToAll(data, string.Format(" AccountId CharacterId Nick Access Level Docked? DockedAt Position")); + foreach (Character c in data.SessionManager.SelectedCharacters.Where(x => x.ZoneId == zoneid)) + { + SendMessageToAll(data, string.Format(" {0} {1} {2} {3} {4} {5} {6}", + c.AccountId, c.Id, c.Nick, c.AccessLevel, c.IsDocked, c.GetCurrentDockingBase().Eid, c.GetPlayerRobotFromZone().CurrentPosition)); + } + } + [ChatCommand("CountOfPlayers")] + public static void CountOfPlayers(AdminCommandData data) + { + foreach (IZone z in data.Request.Session.ZoneMgr.Zones) + { + SendMessageToAll(data, string.Format("Players On Zone {0}: {1}", z.Id, z.Players.ToList().Count)); + } + } + [ChatCommand("AddToChannel")] + public static void AddToChannel(AdminCommandData data) + { + _ = int.TryParse(data.Command.Args[0], out int characterid); + + Sessions.ISession c = data.SessionManager.GetByCharacter(characterid); + + data.ChannelManager.JoinChannel(data.Channel.Name, c.Character, ChannelMemberRole.Operator, string.Empty); + + SendMessageToAll(data, string.Format("Added character {0} to channel ", c.Character.Nick)); + } + [ChatCommand("RemoveFromChannel")] + public static void RemoveFromChannel(AdminCommandData data) + { + _ = int.TryParse(data.Command.Args[0], out int characterid); + + Sessions.ISession c = data.SessionManager.GetByCharacter(characterid); + + data.ChannelManager.LeaveChannel(data.Channel.Name, c.Character); + + SendMessageToAll(data, string.Format("Removed character {0} from channel ", c.Character.Nick)); + } + [ChatCommand("ListRifts")] + public static void ListRifts(AdminCommandData data) + { + foreach (IZone z in data.Request.Session.ZoneMgr.Zones) + { + IEnumerable rift = z.Units.OfType(); + foreach (Rift r in rift) + { + SendMessageToAll(data, string.Format("Rift - Zone: {0}, Position: ({1})", r.Zone, r.CurrentPosition)); + } + } + } + [ChatCommand("FlagPlayerNameOffensive")] + public static void FlagPlayerNameOffensive(AdminCommandData data) + { + int characterId; + bool isOffensive; + + CheckRequiredArgLength(data, 2); + + try + { + characterId = int.Parse(data.Command.Args[0]); + isOffensive = bool.Parse(data.Command.Args[1]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + //TODO does this work if the character doesnt have a session? + Sessions.ISession characterSession = data.SessionManager.GetByCharacter(characterId); + characterSession.Character.IsOffensiveNick = isOffensive; + + SendMessageToAll(data, string.Format("Player with nick {0} is offensive:{1}", characterSession.Character.Nick, characterSession.Character.IsOffensiveNick)); + } + [ChatCommand("RenameCorp")] + public static void RenameCorp(AdminCommandData data) + { + string currentCorpName = data.Command.Args[0]; + string desiredCorpName = data.Command.Args[1]; + string desiredCorpNick = data.Command.Args[2]; + + CheckRequiredArgLength(data, 3); + + Corporation.IsNameOrNickTaken(desiredCorpName, desiredCorpNick).ThrowIfTrue(ErrorCodes.NameTaken); + Corporation corp = Corporation.GetByName(currentCorpName); + corp.SetName(desiredCorpName, desiredCorpNick); + + SendMessageToAll(data, string.Format("Corp with nick {0} changed to: {1} [{2}]", currentCorpName, desiredCorpName, desiredCorpNick)); + } + [ChatCommand("UnlockAllEP")] + public static void UnlockAllEP(AdminCommandData data) + { + int accountId; + + try + { + accountId = int.Parse(data.Command.Args[0]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + Dictionary dictionary = new Dictionary() + { + { k.accountID, accountId } + }; + + string cmd = string.Format("{0}:relay:{1}", Commands.ExtensionFreeAllLockedEpCommand.Text, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, "unlockallep: " + dictionary.ToDebugString()); + } + [ChatCommand("EPBonusSet")] + public static void EPBonusSet(AdminCommandData data) + { + int bonusBoost; + int hours; + + CheckRequiredArgLength(data, 2); + + try + { + bonusBoost = int.Parse(data.Command.Args[0]); + hours = int.Parse(data.Command.Args[1]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + Dictionary dictionary = new Dictionary() + { + { k.bonus, bonusBoost }, + { k.duration, hours } + }; + + string cmd = string.Format("{0}:relay:{1}", Commands.EPBonusSet.Text, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, "EP Bonus Set with command: " + dictionary.ToDebugString()); + } + [ChatCommand("ListRelics")] + public static void ListRelics(AdminCommandData data) + { + Character character = data.Request.Session.Character; + IZone zone = null; + + if (data.Command.Args.Length == 1) + { + bool err = !int.TryParse(data.Command.Args[0], out int zoneCommand); + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + int zoneid = zoneCommand; + zone = data.Request.Session.ZoneMgr.GetZone(zoneid); + } + else if (character.ZoneId != null) + { + zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + } + + if (zone == null) + { + SendMessageToAll(data, "Zone not provided or not found"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + _ = new Dictionary() + { + {"zoneid", zone.Id } + }; + if (zone.RelicManager != null) + { + List> relicDictList = zone.RelicManager.GetRelicListDictionary(); + foreach (Dictionary dict in relicDictList) + { + SendMessageToAll(data, dict.ToDebugString()); + } + } + else + { + SendMessageToAll(data, "This zone does NOT support relics!"); + } + } + [ChatCommand("SetWeather")] + public static void SetWeather(AdminCommandData data) + { + if (data.Command.Args.Length < 2) + { + SendMessageToAll(data, $"bad or missing args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + int zoneId = data.Sender.ZoneId ?? -1; + int weatherInt; + int seconds; + try + { + weatherInt = int.Parse(data.Command.Args[0]); + seconds = int.Parse(data.Command.Args[1]); + if (data.Command.Args.Length > 2) + { + zoneId = int.Parse(data.Command.Args[2]); + } + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + CheckZoneId(data, zoneId); + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + WeatherInfo current = zone.Weather.GetCurrentWeather(); + WeatherInfo weather = new WeatherInfo(current.Next, weatherInt.Min(255), TimeSpan.FromSeconds(seconds)); + zone.Weather.SetCurrentWeather(weather); + SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather()}"); + } + [ChatCommand("GetWeather")] + public static void GetWeather(AdminCommandData data) + { + int zoneId = data.Sender.ZoneId ?? -1; + bool err = false; + if (!data.Command.Args.IsNullOrEmpty()) + { + err = !int.TryParse(data.Command.Args[0], out zoneId); + } + if (err) + { + SendMessageToAll(data, $"Bad or missing zone id"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + CheckZoneId(data, zoneId); + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather()}"); + } + #endregion + #region DevCommands + [ChatCommand("ZoneCleanObstacleBlocking")] + public static void ZoneCleanObstacleBlocking(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + string cmd = string.Format("zoneCleanObstacleBlocking:zone_{0}:null", data.Sender.ZoneId); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneDrawBlockingByEid")] + public static void ZoneDrawBlockingByEid(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + bool err = !long.TryParse(data.Command.Args[0], out long eid); + + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "eid", eid } + }; + + string cmd = string.Format("zoneDrawBlockingByEid:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneRemoveObjectByEid")] + public static void ZoneRemoveObjectByEid(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = !long.TryParse(data.Command.Args[0], out long eid); + + Dictionary dictionary = new Dictionary() + { + { "target", eid } + }; + + string cmd = string.Format("zoneRemoveObject:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneCreateIsland")] + public static void ZoneCreateIsland(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int zoneId = data.Sender.ZoneId ?? -1; + int lvl; + try + { + lvl = int.Parse(data.Command.Args[0]); + if (data.Command.Args.Length > 1) + { + zoneId = int.Parse(data.Command.Args[1]); + } + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + CheckZoneId(data, zoneId); + + Dictionary dictionary = new Dictionary() { { k.low, lvl } }; + + string cmd = string.Format("zoneCreateIsland:zone_{0}:{1}", zoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"zoneCreateIsland accepted: {dictionary.ToDebugString()} \r\nplease wait... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Complete!"); + } + [ChatCommand("ZoneCreateTerraformLimit")] + public static void ZoneCreateTerraformLimit(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = data.Sender.ZoneId ?? -1; + int radius = 0; + string mode; + int zoneId; + try + { + mode = data.Command.Args[0]; + if (mode != "clear") + { + radius = int.Parse(data.Command.Args[1]); + } + + zoneId = int.Parse(data.Command.Args[2]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + CheckZoneId(data, zoneId); + + Dictionary dictionary = new Dictionary() + { + { k.distance, radius }, + { k.mode, mode } + }; + + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateTerraformLimit.Text, zoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"ZoneCreateTerraformLimit accepted: {dictionary.ToDebugString()} \r\nplease wait... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Complete!"); + } + [ChatCommand("ZoneSetLayerWithBitMap")] + public static void ZoneSetLayerWithBitMap(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = data.Sender.ZoneId ?? -1; + string fileName; + int flagValue; + int zoneId; + try + { + string flagName = data.Command.Args[0]; + fileName = data.Command.Args[1]; + flagValue = (int)EnumHelper.GetEnumFromName(flagName); + zoneId = int.Parse(data.Command.Args[2]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + CheckZoneId(data, zoneId); + + Dictionary dictionary = new Dictionary() + { + { k.file, fileName }, + { k.flags, flagValue } + }; + + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneSetLayerWithBitMap.Text, zoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"ZoneSetLayerWithBitMap accepted: {dictionary.ToDebugString()} \r\nplease wait... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Complete!"); + } + [ChatCommand("ZonePlaceWall")] + public static void ZonePlaceWall(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + string cmd = string.Format("zonePlaceWall:zone_{0}:null", data.Sender.ZoneId); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneClearWalls")] + public static void ZoneClearWalls(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + string cmd = string.Format("zoneClearWalls:zone_{0}:null", data.Sender.ZoneId); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneAddDecor")] + public static void ZoneAddDecor(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int? zoneId = data.Sender.ZoneId; + int definition; + int x; + int y; + int z; + double qx; + double qy; + double qz; + double qw; + double scale; + int cat; + + try + { + if (zoneId == null) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + definition = int.Parse(data.Command.Args[0]); + x = int.Parse(data.Command.Args[1]); + y = int.Parse(data.Command.Args[2]); + z = int.Parse(data.Command.Args[3]); + qx = double.Parse(data.Command.Args[4]); + qy = double.Parse(data.Command.Args[5]); + qz = double.Parse(data.Command.Args[6]); + qw = double.Parse(data.Command.Args[7]); + scale = double.Parse(data.Command.Args[8]); + cat = int.Parse(data.Command.Args[9]); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + string cmd() + { + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "x", x*256 }, + { "y", y*256 }, + { "z", z*256 }, + { "quaternionX", qx }, + { "quaternionY", qy }, + { "quaternionZ", qz }, + { "quaternionW", qw }, + { "scale", scale }, + { "category", cat } + }; + + return string.Format("zoneDecorAdd:zone_{0}:{1}", zoneId, GenxyConverter.Serialize(dictionary)); + } + + HandleLocalRequest(data, cmd()); + } + [ChatCommand("ZoneAddDecorToLockedTile")] + public static void ZoneAddDecorToLockedTile(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + + if (!(player.GetPrimaryLock() is TerrainLock terrainLock)) + { + return; + } + + double x = terrainLock.Location.X; + double y = terrainLock.Location.Y; + double z = terrainLock.Location.Z; + + int definition; + double scale; + + try + { + definition = int.Parse(data.Command.Args[0]); + scale = double.Parse(data.Command.Args[1]); + } + catch (Exception ex) + { + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "x", (int)x*256 }, + { "y", (int)y*256 }, + { "z", (int)z*256 }, + { "quaternionX", (double)0 }, + { "quaternionY", (double)0 }, + { "quaternionZ", (double)0 }, + { "quaternionW", (double)0 }, + { "scale", scale }, + { "category", 1 } + }; + + string cmd = string.Format("zoneDecorAdd:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneDeleteDecor")] + public static void ZoneDeleteDecor(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = !int.TryParse(data.Command.Args[0], out int idno); + + Dictionary dictionary = new Dictionary() + { + { "ID", idno } + }; + + string cmd = string.Format("zoneDecorDelete:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneClearLayer")] + public static void ZoneClearLayer(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + string layerName; + int zoneId = data.Sender.ZoneId ?? -1; + try + { + layerName = data.Command.Args[0]; + if (data.Command.Args.Length > 1) + { + zoneId = int.Parse(data.Command.Args[1]); + } + } + catch (Exception ex) + { + SendMessageToAll(data, "Bad args"); + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + CheckZoneId(data, zoneId); + + Dictionary dictionary = new Dictionary() { { k.layerName, layerName } }; + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneClearLayer.Text, zoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"{Commands.ZoneClearLayer.Text} executed on {zoneId} for layer: {layerName}"); + } + [ChatCommand("ZoneSetPlantSpeed")] + public static void ZoneSetPlantSpeed(AdminCommandData data) + { + if (!IsDevModeEnabled(data) && data.Sender.AccessLevel == AccessLevel.normal) + { + return; + } + + _ = !int.TryParse(data.Command.Args[0], out int speed); + + Dictionary dictionary = new Dictionary() + { + { "speed", speed } + }; + + string cmd = string.Format("zoneSetPlantsSpeed:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneSetPlantMode")] + public static void ZoneSetPlantMode(AdminCommandData data) + { + if (!IsDevModeEnabled(data) && data.Sender.AccessLevel == AccessLevel.normal) + { + return; + } + + Dictionary dictionary = new Dictionary() + { + { "mode", data.Command.Args[0] } + }; + + string cmd = string.Format("zoneSetPlantsMode:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneRestoreOriginalGamma")] + public static void ZoneRestoreOriginalGamma(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = new Dictionary() + { + { "mode", data.Command.Args[0] } + }; + + string cmd = string.Format("zoneRestoreOriginalGamma:zone_{0}:null", data.Sender.ZoneId); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneDrawBlockingByDefinition")] + public static void ZoneDrawBlockingByDefinition(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int zoneId = data.Sender.ZoneId ?? -1; + int[] defs; + try + { + List list = new List(); + zoneId = int.Parse(data.Command.Args[0]); + defs = list.AddMany(data.Command.Args.Skip(1).Select(s => int.Parse(s))).ToArray(); + } + catch (Exception ex) + { + SendMessageToAll(data, "Bad args"); + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + CheckZoneId(data, zoneId); + + Dictionary dictionary = new Dictionary() + { + { k.definition, defs } + }; + + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneDrawBlockingByDefinition.Text, zoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneAddBlockingToLockedTiles")] + public static void ZoneAddBlockingToLockedTiles(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + List lockedtiles = player.GetLocks(); + + using (new TerrainUpdateMonitor(zone)) + { + foreach (Lock item in lockedtiles) + { + Position pos = (item as TerrainLock).Location; + zone.Terrain.Blocks.SetValue(pos, new BlockingInfo() { Obstacle = true }); + item.Cancel(); // cancel this lock. we processed it. + } + } + + SendMessageToAll(data, string.Format("Added Blocking To {0} Tiles.", lockedtiles.Count)); + } + [ChatCommand("ZoneRemoveBlockingToLockedTiles")] + public static void ZoneRemoveBlockingToLockedTiles(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); + List lockedtiles = player.GetLocks(); + + using (new TerrainUpdateMonitor(zone)) + { + foreach (Lock item in lockedtiles) + { + Position pos = (item as TerrainLock).Location; + zone.Terrain.Blocks.SetValue(pos, new BlockingInfo() { Obstacle = false }); + item.Cancel(); // cancel this lock. we processed it. + } + } + + SendMessageToAll(data, string.Format("Removed Blocking From {0} Tiles.", lockedtiles.Count)); + } + [ChatCommand("ZoneLockDecor")] + public static void ZoneLockDecor(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int id; + int locked; + + try + { + id = int.Parse(data.Command.Args[0]); + locked = int.Parse(data.Command.Args[1]); + } + catch (Exception ex) + { + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + Dictionary dictionary = new Dictionary() + { + { "ID", id }, + { "locked", locked } + }; + + string cmd = string.Format("zoneDecorLock:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneSetTilesHighway")] + public static void ZoneSetTilesHighway(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.Highway); + } + [ChatCommand("ZoneSetTilesConcreteA")] + public static void ZoneSetTilesConcreteA(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.ConcreteA); + } + [ChatCommand("ZoneSetTilesConcreteB")] + public static void ZoneSetTilesConcreteB(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.ConcreteB); + } + [ChatCommand("ZoneSetTilesAntiPlant")] + public static void ZoneSetTilesAntiPlant(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.AntiPlant); + } + [ChatCommand("ZoneSetTilesNPCRestricted")] + public static void ZoneSetTilesNPCRestricted(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.NpcRestricted); + } + [ChatCommand("ZoneSetTilesHighwayPBS")] + public static void ZoneSetTilesHighwayPBS(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.PBSHighway); + } + [ChatCommand("ZoneSetTilesHighwayCombo")] + public static void ZoneSetTilesHighwayCombo(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.HighWayCombo); + } + [ChatCommand("ZoneSetTilesNPCRoaming")] + public static void ZoneSetTilesNPCRoaming(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.Roaming); + } + [ChatCommand("ZoneSetTilesSyndicate")] + public static void ZoneSetTilesSyndicate(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.SyndicateArea); + } + [ChatCommand("ZoneSetTilesTerraformProtect")] + public static void ZoneSetTilesTerraformProtect(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.TerraformProtected); + } + [ChatCommand("ZoneSetTilesTerraformProtectCombo")] + public static void ZoneSetTilesTerraformProtectCombo(AdminCommandData data) + { + ZoneSetTilesControl(data, TerrainControlFlags.TerraformProtectedCombo); + } + [ChatCommand("SaveLayers")] + public static void SaveLayers(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + Dictionary dictionary = new Dictionary(); + if (data.Command.Args.Length == 1) + { + bool err = !int.TryParse(data.Command.Args[0], out int zoneId); + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + dictionary.Add(k.zoneID, zoneId); + } + string cmd = string.Format("{0}:relay:{1}", Commands.ZoneSaveLayer.Text, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"SaveLayers command accepted: {dictionary.ToDebugString()} \r\nSaving... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Layer(s) Saved! "); + } + [ChatCommand("ZoneIslandBlock")] + public static void ZoneIslandBlock(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + Dictionary dictionary = new Dictionary(); + if (data.Command.Args.Length != 1) + { + SendMessageToAll(data, "Missing or too many args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + bool err = !int.TryParse(data.Command.Args[0], out int low); + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + dictionary.Add(k.low, low); + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateIsland.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Islandblocking command accepted: {dictionary.ToDebugString()} \r\nBlocking... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Zone water level blocked! "); + } + [ChatCommand("ZoneCreateGarden")] + public static void ZoneCreateGarden(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int x; + int y; + + Dictionary dictionary = new Dictionary(); + if (data.Command.Args.Length != 2) + { + SendMessageToAll(data, "Missing or too many args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + try + { + x = int.Parse(data.Command.Args[0]); + y = int.Parse(data.Command.Args[1]); + } + catch (Exception ex) + { + SendMessageToAll(data, "Bad args"); + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + dictionary.Add(k.x, x); + dictionary.Add(k.y, y); + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateGarden.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Garden command accepted: {dictionary.ToDebugString()} \r\nPlanting... "); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Garden Created! "); + } + [ChatCommand("ZoneClearGroundType")] + public static void ZoneClearGroundType(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + bool err = false; + + int zoneId = data.Sender.ZoneId ?? -1; + if (data.Command.Args.Length > 0) + { + err = !int.TryParse(data.Command.Args[0], out zoneId); + } + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + CheckZoneId(data, zoneId); + Dictionary dictionary = new Dictionary() { { k.layerName, k.groundType } }; + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneClearLayer.Text, zoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Sending: {cmd}"); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Command completed"); + } + [ChatCommand("ZoneCopyGroundType")] + public static void ZoneCopyGroundType(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + CheckRequiredArgLength(data, 2); + int sourceZone; + int targetZone; + try + { + sourceZone = int.Parse(data.Command.Args[0]); + targetZone = int.Parse(data.Command.Args[1]); + } + catch (Exception ex) + { + SendMessageToAll(data, "Bad args"); + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + if (sourceZone == targetZone) + { + SendMessageToAll(data, "copy to/from cannot be the same zone id"); + return; + } + CheckZoneId(data, sourceZone); + CheckZoneId(data, targetZone); + Dictionary dictionary = new Dictionary() { { k.source, sourceZone }, { k.target, targetZone } }; + string cmd = string.Format("{0}:relay:{1}", Commands.ZoneCopyGroundType.Text, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Sending: {cmd}"); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Command completed"); + } + [ChatCommand("ZoneRandomFillGroundType")] + public static void ZoneRandomFillGroundType(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int radius = 200; + int iterations = 10; + int zoneId = data.Sender.ZoneId ?? -1; + if (data.Command.Args.Length > 0) + { + if (!int.TryParse(data.Command.Args[0], out radius)) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + if (data.Command.Args.Length > 1) + { + if (!int.TryParse(data.Command.Args[1], out iterations)) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + if (data.Command.Args.Length > 2) + { + if (!int.TryParse(data.Command.Args[2], out zoneId)) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + } + } + } + + CheckZoneId(data, zoneId); + Dictionary dictionary = new Dictionary(){ + { k.size, radius }, + { k.numberOfRuns, iterations } + }; + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneFillGroundTypeRandom.Text, zoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Sending: {cmd}"); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Command completed"); + } + [ChatCommand("ZoneSmoothAll")] + public static void ZoneUpdateSlope(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + int zoneId = data.Sender.ZoneId ?? -1; + + try + { + if (data.Command.Args.Length > 0) + { + zoneId = int.Parse(data.Command.Args[0]); + } + } + catch (Exception ex) + { + SendMessageToAll(data, "Bad args"); + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + CheckZoneId(data, zoneId); + string cmd = string.Format("{0}:zone_{1}:null", Commands.ZoneSmooth.Text, zoneId); + SendMessageToAll(data, $"Sending: {cmd}"); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, $"Command completed"); + } + [ChatCommand("ZoneLockLayers")] + public static void ZoneLockLayers(AdminCommandData data) + { + LockOrUnlockZoneLayers(data, true); + } + [ChatCommand("ZoneUnlockLayers")] + public static void ZoneUnlockLayers(AdminCommandData data) + { + LockOrUnlockZoneLayers(data, false); + } + [ChatCommand("TestMissions")] + public static void TestMissions(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + _ = int.TryParse(data.Command.Args[0], out int charID); + _ = int.TryParse(data.Command.Args[1], out int zoneID); + _ = int.TryParse(data.Command.Args[2], out int level); + _ = int.TryParse(data.Command.Args[3], out int numAttempts); + _ = int.TryParse(data.Command.Args[4], out int displayFlag); + _ = int.TryParse(data.Command.Args[5], out int singleFlag); + Dictionary dictionary = new Dictionary() + { + { k.characterID, charID }, + { k.zone, zoneID }, + { k.level, level }, + { "display", displayFlag }, + { "attempts", numAttempts }, + { "single", singleFlag }, + }; + + string cmd = string.Format("{0}:relay:{1}", Commands.MissionResolveTest.Text, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + SendMessageToAll(data, string.Format("Running missionresolve test {0}", dictionary.ToDebugString())); + } + [ChatCommand("SpawnRelic")] + public static void SpawnRelic(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + { + return; + } + + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); + + + int x, y, zoneid; + + int xCommand; + int yCommand; + int zoneCommand; + + try + { + xCommand = int.Parse(data.Command.Args[0]); + yCommand = int.Parse(data.Command.Args[1]); + zoneCommand = int.Parse(data.Command.Args[2]); + } + catch (Exception ex) + { + SendMessageToAll(data, "Bad args"); + if (ex is ArgumentNullException) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + throw; + } + + if (!(player.GetPrimaryLock() is TerrainLock terrainLock)) + { + if (data.Command.Args.Length != 3) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + x = xCommand; + y = yCommand; + zoneid = zoneCommand; + zone = data.Request.Session.ZoneMgr.GetZone(zoneid); + if (zone == null) + { + SendMessageToAll(data, "Bad zone id!"); + } + } + else + { + x = terrainLock.Location.intX; + y = terrainLock.Location.intY; + zoneid = zone.Id; + } + + Dictionary dictionary = new Dictionary() + { + { "x", x }, + { "y", y }, + {"zoneid", zoneid } + }; + if (zone.RelicManager != null) + { + bool success = zone.RelicManager.ForceSpawnRelicAt(x, y); + if (success) + { + SendMessageToAll(data, "Spawned relic at: " + dictionary.ToDebugString()); + } + else + { + SendMessageToAll(data, "FAILED to spawn relic at: " + dictionary.ToDebugString()); + } + } + else + { + SendMessageToAll(data, "This zone does NOT support relics!"); + } + } + #endregion + } +} diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs new file mode 100644 index 000000000..f3cb8bf53 --- /dev/null +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -0,0 +1,111 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Sessions; +using Perpetuum.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Perpetuum.Services.Channels.ChatCommands +{ + public class AdminCommandRouter + { + private delegate void CommandDelegate(AdminCommandData data); + + private readonly GlobalConfiguration _config; + private readonly ISessionManager _sessionManager; + private readonly IDictionary _commands; + public AdminCommandRouter(GlobalConfiguration configuration, ISessionManager sessionManager) + { + _config = configuration; + _sessionManager = sessionManager; + + _commands = typeof(AdminCommandHandlers).GetMethods() + .Where(m => m.GetCustomAttributes(typeof(ChatCommand), false).Length > 0) + .Select(m => new KeyValuePair( + ((ChatCommand)m.GetCustomAttribute(typeof(ChatCommand))).Command, + (CommandDelegate)Delegate.CreateDelegate(typeof(CommandDelegate), m))) + .ToDictionary(); + } + + public void TryParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) + { + var isAdmin = IsAdmin(sender); + if (isAdmin) + { + channel.SendMessageToAll(_sessionManager, sender, text); //in the future, it will be displayed only in the secure channel + WriteLogToDb(sender, text); //enhancement todo: put the log in the next part to be able to log all the admin details + ParseAdminCommand(sender, text, request, channel, channelManager); + } + else //not an admin, id and text logged for abuse check + { + WriteLogToDb(sender, text); + } + } + + public bool IsAdminCommand(string text) + { + return text.StartsWith("#"); + } + private bool IsAdmin(Character sender) + { + return sender.AccessLevel == AccessLevel.admin; + } + + private void WriteLogToDb(Character sender, string text) + { + var str_trunc = text; + + if (text.Length > 255) + { + str_trunc = text.Substring(1, 255); //text truncated to avoid big text spam insertion in db + } + + Db.Query().CommandText("insert into adminCommandLog (characterid, acclevel, message) values (@characterid, @acclevel, @text)") + .SetParameter("@characterid", sender.Id) + .SetParameter("@acclevel", (int) sender.AccessLevel) + .SetParameter("@text", str_trunc) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + + } + + private void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) + { + + string[] command = text.Split(new char[] { ',' }); + + var data = AdminCommandData.Create(sender, command, request, channel, channelManager, _sessionManager, _config.EnableDev); + + // Commands can only be issued in secure channel + if (channel.Type == ChannelType.Admin) + { + TryInvokeCommand(data); + return; + } + + // Unless it is the command to secure the channel + if (data.Command.Name == "secure") + { + //channel is secure, the admin command can be displayed + channel.SendMessageToAll(_sessionManager, sender, text); + + AdminCommandHandlers.Secure(data); + return; + } + channel.SendMessageToAll(_sessionManager, sender, "Channel must be secured before sending commands."); + } + + private void TryInvokeCommand(AdminCommandData data) + { + if (_commands.TryGetValue(data.Command.Name, out CommandDelegate commandMethod)) + { + commandMethod(data); + } + else + { + AdminCommandHandlers.Unknown(data); + } + } + } +} diff --git a/src/Perpetuum/Services/Channels/IChannelManager.cs b/src/Perpetuum/Services/Channels/IChannelManager.cs index 8098be9f2..688ae0444 100644 --- a/src/Perpetuum/Services/Channels/IChannelManager.cs +++ b/src/Perpetuum/Services/Channels/IChannelManager.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Perpetuum.Accounting.Characters; +using Perpetuum.Host.Requests; namespace Perpetuum.Services.Channels { @@ -19,7 +20,8 @@ public interface IChannelManager void SetMemberRole(string channelName, Character issuer, Character character, ChannelMemberRole role); void SetPassword(string channelName, Character issuer, string password); void SetTopic(string channelName, Character issuer, string topic); - void Talk(string channelName, Character sender, string message); + void Talk(string channelName, Character sender, string message, IRequest request); + void Announcement(string channelName, Character sender, string message); void KickOrBan(string channelName, Character issuer, Character character, string message, bool ban); void UnBan(string channelName, Character issuer, Character character); @@ -27,5 +29,6 @@ public interface IChannelManager IEnumerable GetChannelsByMember(Character member); IEnumerable GetPublicChannels(); + IEnumerable GetAllChannels(); } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Channels/PreMadeChatMessageNames.cs b/src/Perpetuum/Services/Channels/PreMadeChatMessageNames.cs new file mode 100644 index 000000000..a09ea9046 --- /dev/null +++ b/src/Perpetuum/Services/Channels/PreMadeChatMessageNames.cs @@ -0,0 +1,8 @@ +namespace Perpetuum.Services.Channels +{ + public static class PreMadeChatMessageNames + { + public const string TUTORIAL_ARRIVE = "NewPlayerEnteredTheGame"; + public const string TUTORIAL_FINISH = "NewPlayerFinishedTheTutorial"; + } +} diff --git a/src/Perpetuum/Services/EventServices/EPBonusEventService.cs b/src/Perpetuum/Services/EventServices/EPBonusEventService.cs new file mode 100644 index 000000000..ca89ac01f --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EPBonusEventService.cs @@ -0,0 +1,125 @@ +using Perpetuum.Threading.Process; +using Perpetuum.Threading; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Perpetuum.Services.EventServices +{ + public class EPBonusEventService : Process, IDisposable + { + private readonly TimeSpan THREAD_TIMEOUT = TimeSpan.FromSeconds(1); + private bool _disposedValue = false; + + private TimeSpan _duration; + private TimeSpan _elapsed; + private bool _eventStarted; + private bool _endingEvent; + private int _bonus; + private ReaderWriterLockSlim _lock; + + public EPBonusEventService() + { + _lock = new ReaderWriterLockSlim(); + Init(); + } + + private void Init() + { + using (_lock.Write(THREAD_TIMEOUT)) + { + _bonus = 0; + _duration = TimeSpan.MaxValue; + _elapsed = TimeSpan.Zero; + _eventStarted = false; + _endingEvent = false; + } + } + + public int GetBonus() + { + using (_lock.Read(THREAD_TIMEOUT)) + return _bonus; + } + + public void SetEvent(int bonus, TimeSpan duration) + { + using (_lock.Write(THREAD_TIMEOUT)) + { + _bonus = bonus; + _elapsed = TimeSpan.Zero; + _duration = duration; + _endingEvent = false; + _eventStarted = true; + } + } + + private void EndEvent() + { + using (_lock.Write(THREAD_TIMEOUT)) + { + _bonus = 0; + _elapsed = TimeSpan.Zero; + _duration = TimeSpan.MaxValue; + _eventStarted = false; + _endingEvent = false; + } + } + + public override void Update(TimeSpan time) + { + using (_lock.Read(THREAD_TIMEOUT)) + { + + if (!_eventStarted) + return; + + if (_endingEvent) + return; + } + + using (_lock.Write(THREAD_TIMEOUT)) + { + _elapsed += time; + if (_elapsed < _duration) + return; + _endingEvent = true; + } + + Task.Run(() => EndEvent()); + } + + public override void Stop() + { + base.Stop(); + } + + public override void Start() + { + Init(); + base.Start(); + } + + #region IDisposable Support + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _lock?.Dispose(); + _lock = null; // This shouldn't be necessary but added for good practice + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} diff --git a/src/Perpetuum/Services/EventServices/EventListenerService.cs b/src/Perpetuum/Services/EventServices/EventListenerService.cs new file mode 100644 index 000000000..7c13617fb --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventListenerService.cs @@ -0,0 +1,90 @@ +using Perpetuum.Threading.Process; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; +using Perpetuum.Timers; + +namespace Perpetuum.Services.EventServices +{ + /// + /// Listener Process that processes queue of EventMessages and notifies observers + /// + public class EventListenerService : Process + { + private readonly object _lock = new object(); + private readonly IDictionary> _observers; + private readonly ConcurrentQueue _queue; + + public EventListenerService() + { + _observers = new Dictionary>(); + _queue = new ConcurrentQueue(); + } + + /// + /// Send a message where the type of EventMessage determines which listener is notified + /// + /// EventMessage of the type + public void PublishMessage(IEventMessage message) + { + _queue.Enqueue(message); + } + + public void NotifyListeners(IEventMessage message) + { + lock (_lock) + { + if(_observers.TryGetValue(message.Type, out var list)) + { + foreach (var obs in list) + { + obs.OnNext(message); + } + } + } + } + + /// + /// Listeners are subscribed and instantiated in the Bootstrapper + /// + /// Listener + public void AttachListener(IEventProcessor observer) + { + lock (_lock) + { + if (!_observers.ContainsKey(observer.Type)) + { + _observers.Add(observer.Type, new List() { observer }); + } + else + { + _observers[observer.Type].Add(observer); + } + } + } + + public override void Update(TimeSpan time) + { + var timer = new TimeKeeper(TimeSpan.FromSeconds(1)); + while (!_queue.IsEmpty && !timer.Expired) + { + if (_queue.TryDequeue(out var message)) + { + NotifyListeners(message); + } + } + } + + public override void Stop() + { + base.Stop(); + } + + public override void Start() + { + base.Start(); + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/DirectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/DirectMessage.cs new file mode 100644 index 000000000..93233e006 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/DirectMessage.cs @@ -0,0 +1,19 @@ +using Perpetuum.Accounting.Characters; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + /// + /// Message for sending a message to a character + /// + public class DirectMessage : IEventMessage + { + public EventType Type => EventType.DMEcho; + public Character TargetCharacter { get; private set; } + public string Message { get; private set; } + public DirectMessage(Character target, string message) + { + TargetCharacter = target; + Message = message; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs new file mode 100644 index 000000000..d0511e787 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs @@ -0,0 +1,7 @@ +namespace Perpetuum.Services.EventServices.EventMessages +{ + public interface IEventMessage + { + EventType Type { get; } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs b/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs new file mode 100644 index 000000000..7ae28808f --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs @@ -0,0 +1,19 @@ +namespace Perpetuum.Services.EventServices.EventMessages +{ + /// + /// A Simple EventMessage stub with string content, mostly to demonstrate usage + /// + public class EventMessageSimple : IEventMessage + { + public EventType Type => EventType.undefined; + private string _content; + public EventMessageSimple(string payload) + { + _content = payload; + } + public string GetMessage() + { + return _content; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs new file mode 100644 index 000000000..bd5fff3cf --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs @@ -0,0 +1,14 @@ +using Perpetuum.Services.Daytime; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class GameTimeMessage : IEventMessage + { + public EventType Type => EventType.Environmental; + public GameTimeInfo TimeInfo { get; private set; } + public GameTimeMessage(GameTimeInfo time) + { + TimeInfo = time; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs new file mode 100644 index 000000000..3ca039c9f --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs @@ -0,0 +1,32 @@ +using Perpetuum.Players; +using Perpetuum.Units; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + + /// + /// EventMessage sent by an NPC + /// + public class NpcMessage : IEventMessage + { + public EventType Type => EventType.NpcChat; + private string _content; + private readonly Unit _source; + + public NpcMessage(string payload, Unit source) + { + _content = payload; + _source = source; + } + + public Player GetPlayerKiller() + { + return _source as Player; + } + + public string GetMessage() + { + return _content; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs new file mode 100644 index 000000000..15371152b --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs @@ -0,0 +1,19 @@ +using Perpetuum.Zones.NpcSystem; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class NpcReinforcementsMessage : IEventMessage + { + public EventType Type => EventType.NpcReinforce; + + public SmartCreature SmartCreature { get; } + + public int ZoneId { get; } + + public NpcReinforcementsMessage(SmartCreature smartCreature, int zoneID) + { + ZoneId = zoneID; + SmartCreature = smartCreature; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcStateMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcStateMessage.cs new file mode 100644 index 000000000..35e564a23 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcStateMessage.cs @@ -0,0 +1,27 @@ +using System; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public enum NpcState + { + Alive, + Dead + } + + /// + /// A Simple EventMessage stub with string content, mostly to demonstrate usage + /// + public class NpcStateMessage : IEventMessage + { + public EventType Type => EventType.NpcState; + public int FlockId { get; } + public NpcState State { get; } + public DateTime TimeStamp { get; } + public NpcStateMessage(int flockId, NpcState state, DateTime time) + { + FlockId = flockId; + State = state; + TimeStamp = time; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs new file mode 100644 index 000000000..28612363d --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs @@ -0,0 +1,26 @@ +using Perpetuum.Zones.Terrains.Materials.Minerals; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public enum OreNodeState + { + Updated, + Removed + } + + public class OreNpcSpawnMessage : IEventMessage + { + public EventType Type => EventType.NpcOre; + + public OreNodeState NodeState { get; } + public MineralNode Node { get; } + public int ZoneId { get; } + + public OreNpcSpawnMessage(MineralNode node, int zoneID, OreNodeState state) + { + ZoneId = zoneID; + Node = node; + NodeState = state; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs new file mode 100644 index 000000000..3a6cf3362 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs @@ -0,0 +1,24 @@ +using Perpetuum.Services.RiftSystem; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class SpawnPortalMessage : IEventMessage + { + public EventType Type => EventType.PortalSpawn; + + public Position SourcePosition { get; private set; } + public int SourceZone { get; private set; } + public CustomRiftConfig RiftConfig { get; private set; } + public SpawnPortalMessage(int sourceZone, Position srcPosition, CustomRiftConfig riftConfig) + { + SourcePosition = srcPosition; + SourceZone = sourceZone; + RiftConfig = riftConfig; + } + + public override string ToString() + { + return $"SpawnPortalMessage:z:{SourceZone};Pos:{SourcePosition};{RiftConfig};"; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs new file mode 100644 index 000000000..c47059f2d --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using Perpetuum.Groups.Corporations; +using Perpetuum.Players; +using Perpetuum.Zones.Intrusion; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + /// + /// An EventMessage of an event to alter Outpost stability (SAP) + /// + public class StabilityAffectingEvent : IEventMessage + { + public EventType Type => EventType.OutpostStability; + + public Corporation Winner { get; private set; } + public Outpost Outpost { get; private set; } + public bool OverrideRelations { get; private set; } + public int StabilityChange { get; private set; } + public int Definition { get; private set; } + public long? Eid { get; private set; } + private List _participants = new List(); + public List Participants + { + get + { + _participants.RemoveAll(p => p == null); + return _participants; + } + private set + { + _participants = value; + } + } + + public static StabilityAffectBuilder Builder() + { + return new StabilityAffectBuilder(); + } + + public bool IsSystemGenerated() + { + return Winner == null; + } + + [CanBeNull] + public Corporation GetWinnerCorporation() + { + if (IsSystemGenerated()) + return Corporation.GetByName("syndicate_police_central"); + return Winner; + } + + public class StabilityAffectBuilder + { + private Outpost _outpost; + private Corporation _winnerCorp; + private int _definition; + private long? _entityId; + private int _sapPoints; + private List _participants = new List(); + private bool _overrideRelations = false; + + public StabilityAffectingEvent Build() + { + var s = new StabilityAffectingEvent + { + Outpost = _outpost, + Winner = _winnerCorp, + Participants = _participants, + Definition = _definition, + Eid = _entityId, + StabilityChange = _sapPoints, + OverrideRelations = _overrideRelations + }; + return s; + } + + public StabilityAffectBuilder WithOutpost(Outpost outpost) + { + _outpost = outpost; + return this; + } + public StabilityAffectBuilder AddParticipant(Player player) + { + _participants.Add(player); + return this; + } + public StabilityAffectBuilder AddParticipants(IList players) + { + _participants.AddMany(players); + return this; + } + public StabilityAffectBuilder WithWinnerCorp(long corpEid) + { + _winnerCorp = Corporation.Get(corpEid); + return this; + } + public StabilityAffectBuilder WithSapDefinition(int definition) + { + _definition = definition; + return this; + } + public StabilityAffectBuilder WithSapEntityID(long eid) + { + _entityId = eid; + return this; + } + public StabilityAffectBuilder WithPoints(int sapPoints) + { + _sapPoints = sapPoints; + return this; + } + public StabilityAffectBuilder WithOverrideRelations(bool isOverride) + { + _overrideRelations = isOverride; + return this; + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs new file mode 100644 index 000000000..96a6d6a0b --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs @@ -0,0 +1,16 @@ +using Perpetuum.Services.Weather; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class WeatherEventMessage : IEventMessage + { + public EventType Type => EventType.Environmental; + public WeatherInfo Weather { get; private set; } + public int ZoneId { get; private set; } + public WeatherEventMessage(WeatherInfo weather, int zoneId) + { + Weather = weather; + ZoneId = zoneId; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs new file mode 100644 index 000000000..8ca71f38f --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs @@ -0,0 +1,30 @@ +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones.Intrusion; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + /// + /// Processes StabilityAffectingEvent messages for the provided Outpost + /// + public class AffectOutpostStability : EventProcessor + { + private readonly Outpost _outpost; + public AffectOutpostStability(Outpost outpost) + { + _outpost = outpost; + } + + public override EventType Type => EventType.OutpostStability; + + public override void HandleMessage(IEventMessage value) + { + if (value is StabilityAffectingEvent msg) + { + if (msg.Outpost.Equals(_outpost)) + { + _outpost.IntrusionEvent(msg); + } + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs new file mode 100644 index 000000000..80ab7435b --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs @@ -0,0 +1,92 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; +using Perpetuum.Units; +using Perpetuum.Zones; +using System.Collections.Generic; + +namespace Perpetuum.Services.EventServices +{ + /// + /// Simple EventProcessor example for demonstrating the EventListener system + /// + public class ChatEcho : EventProcessor + { + private readonly IChannelManager _channelManager; + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; + private readonly Character _announcer; + + public ChatEcho(IChannelManager channelManager) + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + _channelManager = channelManager; + } + + public override EventType Type => EventType.undefined; + public override void HandleMessage(IEventMessage value) + { + if (value is EventMessageSimple msg) + { + _channelManager.Announcement("General chat", _announcer, msg.GetMessage()); + } + } + } + + public class DirectMessenger : EventProcessor + { + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; + private readonly Character _announcer; + + public DirectMessenger() + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + } + + public override EventType Type => EventType.DMEcho; + public override void HandleMessage(IEventMessage value) + { + if (value is DirectMessage msg) + { + var data = new Dictionary + { + { k.sender, _announcer.Id }, + { k.target, msg.TargetCharacter.Id }, + { k.message, msg.Message } + }; + Message.Builder.SetCommand(Commands.Chat).WithData(data).ToCharacter(msg.TargetCharacter).Send(); + } + } + } + + /// + /// NPC vicinity Chat Echo event handler + /// Emits a message from an NPC on the Vicinity chat channel + /// + public class NpcChatEcho : EventProcessor + { + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; //TODO "Nian" character + private readonly Character _announcer; + + public NpcChatEcho() + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + } + + public override EventType Type => EventType.NpcChat; + public override void HandleMessage(IEventMessage value) + { + if (value is NpcMessage msg) + { + var src = msg.GetPlayerKiller(); + using (var chatPacket = new Packet(ZoneCommand.LocalChat)) + { + chatPacket.AppendInt(_announcer.Id); + chatPacket.AppendUtf8String(msg.GetMessage() + "\r\n"); + src.SendPacketToWitnessPlayers(chatPacket, true); + } + } + + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs new file mode 100644 index 000000000..61391902d --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -0,0 +1,106 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Services.Daytime; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.Weather; +using Perpetuum.Zones; +using Perpetuum.Zones.Effects.ZoneEffects; +using System; +using System.Collections.Generic; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + /// + /// Environment Event processor to modify ZoneEffects on a zone based on weather and time of day + /// + public class EnvironmentalEffectHandler : EventProcessor + { + private readonly IZone _zone; + private WeatherInfo _weatherState; + private GameTimeInfo _gameTime; + private ZoneEffect _currentEffect; + private readonly IDictionary, ZoneEffect> _effects = new Dictionary, ZoneEffect>(); + + public EnvironmentalEffectHandler(IZone zone) + { + _zone = zone; + _effects = InitEffectCollection(); + } + + private IDictionary, ZoneEffect> InitEffectCollection() + { + var dict = new Dictionary, ZoneEffect>() + { + { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.NEUTRAL_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_day, true) }, + { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.GOOD_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_day_clear, true) }, + { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.BAD_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_day_overcast, true) }, + { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.NEUTRAL_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_night, true) }, + { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.GOOD_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_night_clear, true) }, + { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.BAD_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_night_overcast, true) }, + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.GOOD_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_weather_good, true) }, + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.BAD_WEATHER), new ZoneEffect(_zone.Id, EffectType.effect_weather_bad, true) }, + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.NEUTRAL_WEATHER), null } + }; + return dict; + } + + private ZoneEffect GetEffect(GameTimeInfo.DayState dayState, WeatherInfo.WeatherState weatherState) + { + var lookupEffect = Tuple.Create(dayState, weatherState); + if (_effects.TryGetValue(lookupEffect, out ZoneEffect effect)) + { + return effect; + } + return null; + } + + private void OnStateChange() + { + if (_gameTime == null || _weatherState == null) + return; + + var nextEffect = GetEffect(_gameTime.GetDayState(), _weatherState.getWeatherState()); + + var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || + (_currentEffect != null && _currentEffect.Equals(nextEffect)); + + if (!isSameEffect) + { + _zone.ZoneEffectHandler.RemoveEffect(_currentEffect); + _zone.ZoneEffectHandler.AddEffect(nextEffect); + _currentEffect = nextEffect; + } + } + + private bool TryGetWeatherMessage(IEventMessage value) + { + var msg = value as WeatherEventMessage; + var isValidMsg = msg != null && msg.ZoneId == _zone.Id; + if (isValidMsg) + { + _weatherState = msg.Weather; + } + return isValidMsg; + } + + private bool TryGetGameTimeMessage(IEventMessage value) + { + var msg = value as GameTimeMessage; + var isValidMsg = msg != null; + if (isValidMsg) + { + _gameTime = msg.TimeInfo; + } + return isValidMsg; + } + + public override EventType Type => EventType.Environmental; + public override void HandleMessage(IEventMessage value) + { + var stateChange = TryGetWeatherMessage(value) || TryGetGameTimeMessage(value); + if (stateChange) + { + OnStateChange(); + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs new file mode 100644 index 000000000..bf90eee8a --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs @@ -0,0 +1,33 @@ +using Perpetuum.Services.EventServices.EventMessages; +using System; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + public interface IEventProcessor : IObserver + { + void HandleMessage(IEventMessage value); + EventType Type { get; } + } + + public abstract class EventProcessor : IEventProcessor + { + public abstract EventType Type { get; } + + public abstract void HandleMessage(IEventMessage value); + + public void OnNext(IEventMessage value) + { + HandleMessage(value); + } + + public void OnCompleted() + { + throw new NotImplementedException(); + } + + public void OnError(Exception error) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs new file mode 100644 index 000000000..dc8788f37 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -0,0 +1,126 @@ +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Units; +using Perpetuum.Zones; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Reinforcements; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers +{ + public class NpcReinforcementSpawner : NpcSpawnEventHandler + { + protected override TimeSpan SPAWN_DELAY { get { return TimeSpan.FromSeconds(5); } } + protected override TimeSpan SPAWN_LIFETIME { get { return TimeSpan.FromMinutes(30); } } + protected override int MAX_SPAWN_DIST { get { return 15; } } + + public override EventType Type => EventType.NpcReinforce; + + private readonly IDictionary _reinforcementsByNpc = new Dictionary(); + public NpcReinforcementSpawner(IZone zone, INpcReinforcementsRepository reinforcementsRepo) : base(zone, reinforcementsRepo) { } + + protected override IEnumerable GetActiveReinforcments(Presence presence) + { + return _reinforcementsByNpc.Where(p => p.Value.HasActivePresence(presence)).Select(p => p.Value); + } + + protected override bool CheckMessage(IEventMessage inMsg, out NpcReinforcementsMessage msg) + { + if (inMsg is NpcReinforcementsMessage message && _zone.Id == message.ZoneId) + { + msg = message; + return true; + } + else + { + msg = null; + return false; + } + } + + protected override void CheckReinforcements(NpcReinforcementsMessage msg) + { + var info = msg.SmartCreature.BossInfo; + if (!_reinforcementsByNpc.ContainsKey(info)) + { + var reinforcements = _npcReinforcementsRepo.CreateNpcBossAddSpawn(info, msg.ZoneId); + _reinforcementsByNpc.Add(info, reinforcements); + } + } + + protected override bool CheckState(NpcReinforcementsMessage msg) + { + if (msg.SmartCreature.BossInfo.IsDead) + { + CleanupAllReinforcements(msg); + return true; + } + UpdateAggro(msg); + return false; + } + + private void UpdateAggro(NpcReinforcementsMessage msg) + { + var info = msg.SmartCreature.BossInfo; + if (_reinforcementsByNpc.ContainsKey(info)) + { + var activeWaves = _reinforcementsByNpc[info].GetAllActiveWaves().Where(w => w.ActivePresence != null); + foreach (var wave in activeWaves) + { + SpreadAggro(wave.ActivePresence, msg.SmartCreature); + } + } + } + + protected override void CleanupAllReinforcements(NpcReinforcementsMessage msg) + { + var info = msg.SmartCreature.BossInfo; + if (_reinforcementsByNpc.ContainsKey(info)) + { + var activeWaves = _reinforcementsByNpc[info].GetAllActiveWaves(); + foreach (var wave in activeWaves) + { + ExpireWave(wave); + } + _reinforcementsByNpc.Remove(info); + } + } + + protected override Position FindSpawnPosition(NpcReinforcementsMessage msg, int maxRange) + { + var finder = new RandomWalkableAroundPositionFinder(_zone, msg.SmartCreature.CurrentPosition, maxRange); + if (finder.Find(out Position result)) + { + return result; + } + return msg.SmartCreature.CurrentPosition; + } + + protected override INpcReinforcementWave GetNextWave(NpcReinforcementsMessage msg) + { + var npc = msg.SmartCreature; + var percent = 1.0 - npc.ArmorPercentage; + return _reinforcementsByNpc[npc.BossInfo].GetNextPresence(percent); + } + + protected override void OnSpawning(Presence pres, NpcReinforcementsMessage msg) + { + SpreadAggro(pres, msg.SmartCreature); + } + + private void SpreadAggro(Presence presenceToAggro, SmartCreature smartCreatureWithAggro) + { + foreach (var npc in presenceToAggro.Flocks.GetMembers()) + { + foreach (var threat in smartCreatureWithAggro.ThreatManager.Hostiles) + { + npc.AddDirectThreat(threat.Unit, threat.Threat + FastRandom.NextDouble(5, 10)); + } + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs new file mode 100644 index 000000000..f89c735a5 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs @@ -0,0 +1,127 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Reinforcements; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers +{ + /// + /// Common functions and event handling procedure for NpcReinforcement related events + /// + /// EventMessage + public abstract class NpcSpawnEventHandler : EventProcessor where T : IEventMessage + { + protected abstract TimeSpan SPAWN_DELAY { get; } + protected abstract TimeSpan SPAWN_LIFETIME { get; } + protected abstract int MAX_SPAWN_DIST { get; } + protected readonly IZone _zone; + + protected readonly INpcReinforcementsRepository _npcReinforcementsRepo; + public NpcSpawnEventHandler(IZone zone, INpcReinforcementsRepository reinforcementsRepo) + { + _zone = zone; + _npcReinforcementsRepo = reinforcementsRepo; + } + + protected abstract IEnumerable GetActiveReinforcments(Presence presence); + + protected abstract bool CheckMessage(IEventMessage inMsg, out T msg); + + protected abstract void CheckReinforcements(T msg); + + protected abstract bool CheckState(T msg); + + protected abstract void CleanupAllReinforcements(T msg); + + protected abstract Position FindSpawnPosition(T msg, int maxRange); + + protected abstract INpcReinforcementWave GetNextWave(T msg); + + protected virtual void OnSpawning(Presence pres, T msg) { } + + protected virtual Position GetHomePos(T msg, Position spawnPos) + { + return spawnPos; + } + + protected void DoBeams(Position beamLocation) + { + _zone.CreateBeam(BeamType.npc_egg_beam, b => b.WithPosition(beamLocation).WithDuration(SPAWN_DELAY)); + _zone.CreateBeam(BeamType.teleport_storm, b => b.WithPosition(beamLocation).WithDuration(SPAWN_DELAY)); + } + + protected void DoSpawning(INpcReinforcementWave wave, Position homePosition, Position spawnPosition, T msg) + { + var pres = _zone.AddDynamicPresenceToPosition(wave.PresenceId, homePosition, spawnPosition, SPAWN_LIFETIME); + OnSpawning(pres, msg); + pres.PresenceExpired += OnPresenceExpired; + wave.SetActivePresence(pres); + } + + protected void OnPresenceExpired(Presence presence) + { + var activeReinforcements = GetActiveReinforcments(presence); + foreach (var reinforcements in activeReinforcements) + { + var wave = reinforcements.GetActiveWaveOfPresence(presence); + ExpireWave(wave); + } + } + + protected void ExpireWave(INpcReinforcementWave wave) + { + wave.ActivePresence.PresenceExpired -= OnPresenceExpired; + wave.DeactivatePresence(); + } + + private bool _spawning = false; + + public override void HandleMessage(IEventMessage value) + { + if (CheckMessage(value, out T msg)) + { + if (CheckState(msg)) + return; + + if (_spawning) + return; + + CheckReinforcements(msg); + + var wave = GetNextWave(msg); + if (wave == null) + return; // Presence not found for this message state or already spawned + + var spawnPos = FindSpawnPosition(msg, MAX_SPAWN_DIST); + if (spawnPos == Position.Empty) + return; // Failed to find valid spawn location, try again on next cycle + + var homePos = GetHomePos(msg, spawnPos); + + DoBeams(homePos); + + _spawning = true; + Task.Delay(SPAWN_DELAY).ContinueWith(t => + { + try + { + DoSpawning(wave, homePos, spawnPos, msg); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + finally + { + _spawning = false; + } + }); + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs new file mode 100644 index 000000000..342f3edbb --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs @@ -0,0 +1,118 @@ +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.NpcSystem.Reinforcements; +using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.Terrains.Materials.Minerals; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers +{ + /// + /// EventListener for each zone, receives messages for mineralnode mined and spawns npc presence based on the INpcReinforcementsRepository configurations + /// + public class OreNpcSpawner : NpcSpawnEventHandler + { + protected override TimeSpan SPAWN_DELAY { get { return TimeSpan.FromSeconds(10); } } + protected override TimeSpan SPAWN_LIFETIME { get { return TimeSpan.FromHours(3); } } + protected override int MAX_SPAWN_DIST { get { return 100; } } + + private const int MIN_SPAWN_DIST_TOLERANCE = 30; + + public override EventType Type => EventType.NpcOre; + + private readonly IDictionary _reinforcementsByNode = new Dictionary(); + private readonly IEnumerable _mineralConfigs; + + public OreNpcSpawner(IZone zone, INpcReinforcementsRepository reinforcementsRepo, IMineralConfigurationReader mineralConfigurationReader) : base(zone, reinforcementsRepo) + { + _mineralConfigs = mineralConfigurationReader.ReadAll().Where(c => c.ZoneId == zone.Id); + } + + protected override IEnumerable GetActiveReinforcments(Presence presence) + { + return _reinforcementsByNode.Where(p => p.Value.HasActivePresence(presence)).Select(p => p.Value); + } + + protected override bool CheckMessage(IEventMessage inMsg, out OreNpcSpawnMessage msg) + { + if (inMsg is OreNpcSpawnMessage message && _zone.Id == message.ZoneId) + { + msg = message; + return true; + } + else + { + msg = null; + return false; + } + } + + protected override void CheckReinforcements(OreNpcSpawnMessage msg) + { + var node = msg.Node; + if (!_reinforcementsByNode.ContainsKey(node)) + { + var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type, msg.ZoneId); + _reinforcementsByNode.Add(node, oreSpawn); + } + } + + protected override bool CheckState(OreNpcSpawnMessage msg) + { + if (msg.NodeState == OreNodeState.Removed) + { + CleanupAllReinforcements(msg); + return true; + } + return false; + } + + protected override void CleanupAllReinforcements(OreNpcSpawnMessage msg) + { + var node = msg.Node; + if (_reinforcementsByNode.ContainsKey(node)) + { + var activeWaves = _reinforcementsByNode[node].GetAllActiveWaves(); + foreach (var wave in activeWaves) + { + ExpireWave(wave); + } + _reinforcementsByNode.Remove(node); + } + } + + protected override Position FindSpawnPosition(OreNpcSpawnMessage msg, int maxRange) + { + var fieldCenter = msg.Node.Area.Center.ToPosition(); + var finder = new RandomWalkableOnCircle(_zone, fieldCenter, maxRange, MIN_SPAWN_DIST_TOLERANCE); + if (finder.Find(out Position result)) + { + return result; + } + return Position.Empty; + } + + private double ComputeFieldPercentConsumed(MineralNode node) + { + var current = Convert.ToInt32(node.GetTotalAmount()); + var total = _mineralConfigs.Single(c => c.Type == node.Type).TotalAmountPerNode; + var percent = 1.0 - (current / (double)total).Clamp(); + return percent; + } + + protected override INpcReinforcementWave GetNextWave(OreNpcSpawnMessage msg) + { + var node = msg.Node; + var percent = ComputeFieldPercentConsumed(node); + return _reinforcementsByNode[node].GetNextPresence(percent); + } + + protected override Position GetHomePos(OreNpcSpawnMessage msg, Position spawnPos) + { + return msg.Node.Area.Center.ToPosition(); + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs new file mode 100644 index 000000000..c3ad02638 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -0,0 +1,177 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.EntityFramework; +using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones.NpcSystem.Flocks; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Perpetuum.Log; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + public class NpcStateAnnouncer : EventProcessor + { + private readonly IChannelManager _channelManager; + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; + private const string CHANNEL = "Syndicate Radio"; + private readonly Character _announcer; + private readonly IFlockConfigurationRepository _flockConfigReader; + private readonly IDictionary _nameDictionary; + private readonly IDictionary _state; + + public NpcStateAnnouncer(IChannelManager channelManager, IFlockConfigurationRepository flockConfigurationRepo, ICustomDictionary customDictionary) + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + _channelManager = channelManager; + _flockConfigReader = flockConfigurationRepo; + _nameDictionary = customDictionary.GetDictionary(0); + _state = new Dictionary(); + } + + //TODO use ChannelMessageHandler w/ PreMadeChatMessages + private readonly IList _aliveMessages = new List() + { + "has spawned!", + "has appeared on Syndicate scanners", + "has been detected" + }; + + private readonly IList _deathMessages = new List() + { + "has been defeated", + "is no longer a threat to Syndicate activity", + "'s signature is no longer detected at this time" + }; + + private int GetNpcDef(NpcStateMessage msg) + { + var config = _flockConfigReader.Get(msg.FlockId); + return config?.EntityDefault?.Definition ?? -1; + } + + private string GetNpcName(int def) + { + var nameToken = EntityDefault.Get(def).Name + "_name"; + var name = ""; + if (_nameDictionary.TryGetValue(nameToken, out name) != true) + { + WriteNPCStateAnnouncerLog($"Missing localized name for {nameToken}"); + } + return name ?? string.Empty; + } + + private void WriteNPCStateAnnouncerLog(string message) + { + var e = new LogEvent + { + LogType = LogType.Error, + Tag = "NpcStateAnnouncer", + Message = message + }; + + Logger.Log(e); + } + + private string GetStateMessage(NpcStateMessage msg) + { + if (msg.State == NpcState.Alive) + { + return _aliveMessages[FastRandom.NextInt(_aliveMessages.Count - 1)]; + } + else if (msg.State == NpcState.Dead) + { + return _deathMessages[FastRandom.NextInt(_deathMessages.Count - 1)]; + } + return string.Empty; + } + + private string BuildChatAnnouncement(NpcStateMessage msg) + { + var def = GetNpcDef(msg); + if (def < 0) + return string.Empty; + + var npcName = GetNpcName(def); + if (npcName == string.Empty) + return string.Empty; + + var stateMessage = GetStateMessage(msg); + if (stateMessage == string.Empty) + return string.Empty; + + return $"{npcName} {stateMessage}"; + } + + private static string Strip(string str) + { + return Regex.Replace(str, "[^a-zA-Z0-9_.]+", ""); + } + + private static string Abbreviate(string name, int charLim) + { + var words = name.Split(' '); + var perWordLen = (charLim / (words.Length).Max(1)).Clamp(3, 24); + for (var i = 0; i < words.Length; i++) + { + words[i] = Strip(words[i]).Clamp(perWordLen) ?? ""; + } + return string.Join(" ", words); + } + + private string BuildTopicFromState() + { + var topic = "Current: "; + var allowableNameLens = (190 / (_state.Count).Max(1)).Clamp(3, 64); + foreach (var pair in _state) + { + var name = Abbreviate(GetNpcName(pair.Key), allowableNameLens); + if (name == string.Empty) continue; + if(pair.Value.State == NpcState.Alive) + topic += $"{name}|"; + } + return topic; + } + + private bool IsUpdatable(NpcStateMessage current, NpcStateMessage next) + { + return next.TimeStamp > current.TimeStamp; + } + + private bool UpdateState(NpcStateMessage msg) + { + var defKey = GetNpcDef(msg); + if (defKey < 0) + { + return false; + } + else if (!_state.ContainsKey(defKey) || IsUpdatable(_state[defKey], msg)) + { + _state[defKey] = msg; + return true; + } + return false; + } + + public override EventType Type => EventType.NpcState; + public override void HandleMessage(IEventMessage value) + { + if (value is NpcStateMessage msg) + { + if (UpdateState(msg)) + { + var announcement = BuildChatAnnouncement(msg); + var motd = BuildTopicFromState(); + if (!announcement.IsNullOrEmpty()) + { + _channelManager.Announcement(CHANNEL, _announcer, announcement); + } + if (!motd.IsNullOrEmpty()) + { + _channelManager.SetTopic(CHANNEL, _announcer, motd); + } + } + + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs new file mode 100644 index 000000000..ca5338ff6 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs @@ -0,0 +1,76 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Zones; +using Perpetuum.Zones.Finders.PositionFinders; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + public class PortalSpawner : EventProcessor + { + private readonly IEntityServices _entityServices; + private readonly IZoneManager _zoneManager; + + public override EventType Type => EventType.PortalSpawn; + + public PortalSpawner(IEntityServices entityServices, IZoneManager zoneManager) + { + _entityServices = entityServices; + _zoneManager = zoneManager; + } + + private bool ValidateMessage(SpawnPortalMessage msg) + { + if (msg.RiftConfig == null || !_zoneManager.ContainsZone(msg.SourceZone)) + { + return false; + } + else if (msg.SourcePosition == Position.Empty) + { + return false; + } + return msg.SourcePosition.IsValid(_zoneManager.GetZone(msg.SourceZone).Size); + } + + private Position TryGetValidPosition(SpawnPortalMessage msg) + { + var zone = _zoneManager.GetZone(msg.SourceZone); + if (zone.IsWalkable(msg.SourcePosition)) + { + return msg.SourcePosition; + } + var finder = new ClosestWalkablePositionFinder(zone, msg.SourcePosition); + if (finder.Find(out Position result)) + { + return result; + } + return Position.Empty; + } + + public override void HandleMessage(IEventMessage value) + { + if (value is SpawnPortalMessage msg) + { + if (!ValidateMessage(msg)) + { + Logger.Warning($"SpawnPortalMessage was not valid!\n{msg}"); + return; + } + + var spawnPos = TryGetValidPosition(msg); + if (spawnPos == Position.Empty) + { + Logger.Warning($"SpawnPortalMessage's Position was not valid and could not be fixed\n{msg}"); + return; + } + + CustomRiftSpawner.TrySpawnRift(msg.RiftConfig, _zoneManager, msg.SourceZone, spawnPos, () => + { + return (StrongholdEntryRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.TARGETTED_RIFT); + }); + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventType.cs b/src/Perpetuum/Services/EventServices/EventType.cs new file mode 100644 index 000000000..85de191c3 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventType.cs @@ -0,0 +1,16 @@ +namespace Perpetuum.Services.EventServices +{ + public enum EventType + { + undefined, + ChatEcho, + DMEcho, + OutpostStability, + NpcChat, + NpcState, + NpcOre, + NpcReinforce, + Environmental, + PortalSpawn, + } +} diff --git a/src/Perpetuum/Services/GameTime/GameTimeInfo.cs b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs new file mode 100644 index 000000000..a1d5f6460 --- /dev/null +++ b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs @@ -0,0 +1,59 @@ +using System; + +namespace Perpetuum.Services.Daytime +{ + public class GameTimeInfo + { + private const int NIGHT_END = 100; + //private const int SUNRISE = 150; + private const int DAY_START = 200; + private const int DAY_END = 700; + //private const int SUNSET = 750; + private const int NIGHT_START = 800; + private const int DAY_LENGTH = 1000; + + private static readonly double _gameDaySpan = TimeSpan.FromHours(3).TotalHours; + + public static GameTimeInfo FromCurrentTime() + { + return FromTime(DateTime.Now); + } + + public static GameTimeInfo FromTime(DateTime time) + { + var currentDayTime = time.TimeOfDay.TotalHours; + var gtDayFactor = (currentDayTime % _gameDaySpan) / _gameDaySpan; + var gameTime = (int)(gtDayFactor.Clamp() * DAY_LENGTH); + return new GameTimeInfo(gameTime); + } + + public readonly int GameTimeStamp; + public GameTimeInfo(int gameTime) + { + GameTimeStamp = gameTime; + } + + public enum DayState + { + DAY, + NEUTRAL, + NIGHT + } + + public DayState GetDayState() + { + if (GameTimeStamp > DAY_START && GameTimeStamp < DAY_END) + { + return DayState.DAY; + } + else if (GameTimeStamp > NIGHT_START || GameTimeStamp < NIGHT_END) + { + return DayState.NIGHT; + } + else + { + return DayState.NEUTRAL; + } + } + } +} diff --git a/src/Perpetuum/Services/GameTime/GameTimeObserver.cs b/src/Perpetuum/Services/GameTime/GameTimeObserver.cs new file mode 100644 index 000000000..057ccbb78 --- /dev/null +++ b/src/Perpetuum/Services/GameTime/GameTimeObserver.cs @@ -0,0 +1,23 @@ +using Perpetuum.Reactive; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; + +namespace Perpetuum.Services.Daytime +{ + /// + /// Observer responsible for capturing GameTime events from the GameTimeService and sending messages to the EventListenerService + /// + public class GameTimeObserver : Observer + { + private readonly EventListenerService _listener; + public GameTimeObserver(EventListenerService listener) + { + _listener = listener; + } + + public override void OnNext(GameTimeInfo info) + { + _listener.PublishMessage(new GameTimeMessage(info)); + } + } +} diff --git a/src/Perpetuum/Services/GameTime/GameTimeService.cs b/src/Perpetuum/Services/GameTime/GameTimeService.cs new file mode 100644 index 000000000..606f3a75a --- /dev/null +++ b/src/Perpetuum/Services/GameTime/GameTimeService.cs @@ -0,0 +1,52 @@ +using Perpetuum.Reactive; +using Perpetuum.Threading.Process; +using System; + +namespace Perpetuum.Services.Daytime +{ + public class GameTimeService : Process, IGameTimeService + { + private readonly Observable _observable; + private GameTimeInfo _current; + public GameTimeService() + { + _current = GameTimeInfo.FromCurrentTime(); + _observable = new AnonymousObservable(OnSubscribe); + } + + private void RefreshCurrentDayTime() + { + _current = GameTimeInfo.FromCurrentTime(); + } + + public GameTimeInfo GetCurrentDayTime() + { + if (_current == null) + { + RefreshCurrentDayTime(); + } + return _current; + } + + private void OnSubscribe(IObserver observer) + { + observer.OnNext(GetCurrentDayTime()); + } + + public IDisposable Subscribe(IObserver observer) + { + return _observable.Subscribe(observer); + } + + private void SendDayTimeNotification() + { + _observable.OnNext(GetCurrentDayTime()); + } + + public override void Update(TimeSpan time) + { + RefreshCurrentDayTime(); + SendDayTimeNotification(); + } + } +} diff --git a/src/Perpetuum/Services/GameTime/IGameTimeService.cs b/src/Perpetuum/Services/GameTime/IGameTimeService.cs new file mode 100644 index 000000000..e17ff3176 --- /dev/null +++ b/src/Perpetuum/Services/GameTime/IGameTimeService.cs @@ -0,0 +1,14 @@ +using Perpetuum.Threading.Process; +using System; + +namespace Perpetuum.Services.Daytime +{ + /// + /// Service that tracks the in-game time as players experience on all zones + /// + public interface IGameTimeService : IObservable, IProcess + { + [NotNull] + GameTimeInfo GetCurrentDayTime(); + } +} diff --git a/src/Perpetuum/Services/HighScores/HighScoreService.cs b/src/Perpetuum/Services/HighScores/HighScoreService.cs index f8a25f8de..35be6499d 100644 --- a/src/Perpetuum/Services/HighScores/HighScoreService.cs +++ b/src/Perpetuum/Services/HighScores/HighScoreService.cs @@ -19,13 +19,14 @@ public IEnumerable GetHighScores(DateTimeRange range) h.characterid,sum(playerskilled) as pk from characterhighscore h join characters c on h.characterid=c.characterid where - (select acclevel from accounts where accountid=c.accountid)=0 and + (select acclevel from accounts where accountid=c.accountid)=@acctlvl and date between @startDate and @endDate group by h.characterid order by pk desc"; return Db.Query().CommandText(qry) .SetParameter("@startDate", range.Start) .SetParameter("@endDate", range.End) + .SetParameter("@acctlvl", (int)AccessLevel.normal) .Execute() .Select(CreateHighScoreFromRecord).ToArray(); } diff --git a/src/Perpetuum/Services/Insurance/InsuranceHelper.cs b/src/Perpetuum/Services/Insurance/InsuranceHelper.cs index 959ec9f8e..be4ccf010 100644 --- a/src/Perpetuum/Services/Insurance/InsuranceHelper.cs +++ b/src/Perpetuum/Services/Insurance/InsuranceHelper.cs @@ -410,20 +410,15 @@ public static ErrorCodes GetInsurancePrice(int definition, ref double insuranceF insurancePayOut = 0; InsurancePrice insurancePrice; - if (_insurancePrices.TryGetValue(definition, out insurancePrice)) - { - - if (insurancePrice == null) - { - return ErrorCodes.WTFErrorMedicalAttentionSuggested; - } + // get insurance price from cache. + _insurancePrices.TryGetValue(definition, out insurancePrice); - } - else + // not in cache. + // get record from the database and store in our cache. + if (insurancePrice == null) { - var record = Db.Query().CommandText("select fee,payout from insuranceprices where definition=@definition").SetParameter("@definition", definition) - .ExecuteSingleRow(); + var record = Db.Query().CommandText("select fee,payout from insuranceprices where definition=@definition").SetParameter("@definition", definition).ExecuteSingleRow(); if (record == null) { @@ -433,14 +428,13 @@ public static ErrorCodes GetInsurancePrice(int definition, ref double insuranceF } insurancePrice = new InsurancePrice - { - definition = definition, - fee = record.GetValue(0), - payOut = record.GetValue(1) - }; - + { + definition = definition, + fee = record.GetValue(0), + payOut = record.GetValue(1) + }; - _insurancePrices.AddOrUpdate(definition, insurancePrice, (k, v) => v); + _insurancePrices.AddOrUpdate(definition, insurancePrice, (k, v) => insurancePrice); } insuranceFee = insurancePrice.fee; diff --git a/src/Perpetuum/Services/ItemShop/ItemShop.cs b/src/Perpetuum/Services/ItemShop/ItemShop.cs index 04a4825c1..096b28476 100644 --- a/src/Perpetuum/Services/ItemShop/ItemShop.cs +++ b/src/Perpetuum/Services/ItemShop/ItemShop.cs @@ -19,7 +19,7 @@ public class ItemShop : Unit private readonly IStandingHandler _standingHandler; private readonly CharacterWalletFactory _characterWalletFactory; - public ItemShop(IStandingHandler standingHandler,CharacterWalletFactory characterWalletFactory) + public ItemShop(IStandingHandler standingHandler, CharacterWalletFactory characterWalletFactory) { _standingHandler = standingHandler; _characterWalletFactory = characterWalletFactory; @@ -40,9 +40,11 @@ private ItemShopEntry GetEntry(int entryID) { const string qryStr = @"SELECT * FROM dbo.itemshop AS its JOIN itemshoplocations sl ON its.presetid = sl.presetid - WHERE sl.locationeid = @eid AND its.id = @id"; + JOIN entitydefaults d on its.targetdefinition = d.definition + WHERE d.enabled=1 AND d.hidden=0 AND + sl.locationeid = @eid AND its.id = @id"; - var record = Db.Query().CommandText(qryStr).SetParameter("@eid",Eid).SetParameter("@id", entryID).ExecuteSingleRow(); + var record = Db.Query().CommandText(qryStr).SetParameter("@eid", Eid).SetParameter("@id", entryID).ExecuteSingleRow(); if (record == null) return null; @@ -54,9 +56,11 @@ private List GetAll() { const string qryStr = @"SELECT * FROM dbo.itemshop AS its JOIN itemshoplocations sl ON its.presetid = sl.presetid - WHERE sl.locationeid = @eid"; + JOIN entitydefaults d on its.targetdefinition = d.definition + WHERE d.enabled=1 AND d.hidden=0 AND + sl.locationeid = @eid"; - var records = Db.Query().CommandText(qryStr).SetParameter("@eid",Eid).Execute(); + var records = Db.Query().CommandText(qryStr).SetParameter("@eid", Eid).Execute(); var entries = new List(); @@ -82,11 +86,9 @@ private static ItemShopEntry CreateItemShopEntryFromRecord(IDataRecord record) var tmCoin = record.GetValue("tmCoin") ?? 0; var icsCoin = record.GetValue("icscoin") ?? 0; var asiCoin = record.GetValue("asicoin") ?? 0; + var uniCoin = record.GetValue("unicoin") ?? 0; - - - - return new ItemShopEntry(id, EntityDefault.Get(targetDefinition), targetAmount, tmCoin,icsCoin,asiCoin , price, globalLimit, purchaseCount,standing); + return new ItemShopEntry(id, EntityDefault.Get(targetDefinition), targetAmount, tmCoin, icsCoin, asiCoin, uniCoin, price, globalLimit, purchaseCount, standing); } public Dictionary EntriesToDictionary() @@ -96,7 +98,7 @@ public Dictionary EntriesToDictionary() private void CheckStanding(Character character, ItemShopEntry shopEntry) { - if ( shopEntry.Standing == null) + if (shopEntry.Standing == null) return; var standing = _standingHandler.GetStanding(Owner, character.Eid); @@ -114,7 +116,7 @@ public void Buy(Container container, Character character, int entryID, int quant throw new PerpetuumException(ErrorCodes.ItemNotFound); entry.CheckGlobalLimit(quantity); - CheckStanding(character,entry); + CheckStanding(character, entry); entry.RemoveFromContainer(container, quantity); @@ -135,14 +137,14 @@ public void Buy(Container container, Character character, int entryID, int quant var targetItem = entry.CreateTargetItem(character, quantity); container.AddItem(targetItem, true); - UpdateGlobalPurchaseCount(targetItem.Definition,targetItem.Quantity); + UpdateGlobalPurchaseCount(targetItem.Definition, targetItem.Quantity); if (entry.TmCoin > 0) - character.LogTransaction(TransactionLogEvent.Builder() - .SetTransactionType(TransactionType.ItemShopTake) - .SetCharacter(character) - .SetContainer(container) - .SetItem(EntityDefault.GetByName(DefinitionNames.TM_MISSION_COIN).Definition, entry.TmCoin * quantity)); + character.LogTransaction(TransactionLogEvent.Builder() + .SetTransactionType(TransactionType.ItemShopTake) + .SetCharacter(character) + .SetContainer(container) + .SetItem(EntityDefault.GetByName(DefinitionNames.TM_MISSION_COIN).Definition, entry.TmCoin * quantity)); if (entry.IcsCoin > 0) character.LogTransaction(TransactionLogEvent.Builder() @@ -158,6 +160,13 @@ public void Buy(Container container, Character character, int entryID, int quant .SetContainer(container) .SetItem(EntityDefault.GetByName(DefinitionNames.ASI_MISSION_COIN).Definition, entry.AsiCoin * quantity)); + if (entry.UniCoin > 0) + character.LogTransaction(TransactionLogEvent.Builder() + .SetTransactionType(TransactionType.ItemShopTake) + .SetCharacter(character) + .SetContainer(container) + .SetItem(EntityDefault.GetByName(DefinitionNames.UNIVERSAL_MISSION_COIN).Definition, entry.UniCoin * quantity)); + character.LogTransaction(TransactionLogEvent.Builder() .SetTransactionType(TransactionType.ItemShopBuy) diff --git a/src/Perpetuum/Services/ItemShop/ItemShopEntry.cs b/src/Perpetuum/Services/ItemShop/ItemShopEntry.cs index 86d478bd1..a4c5f205c 100644 --- a/src/Perpetuum/Services/ItemShop/ItemShopEntry.cs +++ b/src/Perpetuum/Services/ItemShop/ItemShopEntry.cs @@ -14,12 +14,13 @@ public class ItemShopEntry private readonly int _tmcoin; private readonly int _icscoin; private readonly int _asicoin; + private readonly int _unicoin; private readonly double _credit; private readonly int? _globalLimit; private readonly int _purchaseCount; private readonly double? _standing; - public ItemShopEntry(int id,EntityDefault targetItemED,int targetItemAmount,int tmcoin, int icscoin, int asicoin, double credit, int? globalLimit, int purchaseCount,double? standing) + public ItemShopEntry(int id, EntityDefault targetItemED, int targetItemAmount, int tmcoin, int icscoin, int asicoin, int unicoin, double credit, int? globalLimit, int purchaseCount, double? standing) { _id = id; _targetItemED = targetItemED; @@ -27,6 +28,7 @@ public ItemShopEntry(int id,EntityDefault targetItemED,int targetItemAmount,int _tmcoin = tmcoin; _icscoin = icscoin; _asicoin = asicoin; + _unicoin = unicoin; _credit = credit; _purchaseCount = purchaseCount; _globalLimit = globalLimit; @@ -53,18 +55,20 @@ public int TmCoin get { return _tmcoin; } } - public int AsiCoin { get { return _asicoin; } } - public int IcsCoin { get { return _icscoin; } } + public int UniCoin + { + get { return _unicoin; } + } public double Credit { @@ -78,7 +82,7 @@ public double? Standing public Dictionary ToDictionary() { - var d = new Dictionary(); + var d = new Dictionary(); d[k.ID] = _id; d[k.targetDefinition] = _targetItemED.Definition; @@ -90,10 +94,11 @@ public Dictionary ToDictionary() d[k.tmcoin] = _tmcoin; d[k.icscoin] = _icscoin; d[k.asicoin] = _asicoin; + d[k.unicoin] = _unicoin; return d; } - public Item CreateTargetItem(Character owner,int quantity) + public Item CreateTargetItem(Character owner, int quantity) { var targetItem = (Item)Entity.Factory.CreateWithRandomEID(TargetItemED); targetItem.Owner = owner.Eid; @@ -110,7 +115,7 @@ public void CheckGlobalLimit(int quantity) if (GlobalLimit <= PurchaseCount) throw new PerpetuumException(ErrorCodes.OutOfItemGlobally); - var availableAmount = (int) GlobalLimit - PurchaseCount; + var availableAmount = (int)GlobalLimit - PurchaseCount; //clamp to the available maximum if (availableAmount < quantity) @@ -137,6 +142,12 @@ public void RemoveFromContainer(Container container, int quantity) var ac = Coin.CreateASICoin(_asicoin); ac.RemoveFromContainer(container, quantity); } + + if (_unicoin > 0) + { + var ac = Coin.CreateUniversalCoin(_unicoin); + ac.RemoveFromContainer(container, quantity); + } } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Looting/CompositeLootGenerator.cs b/src/Perpetuum/Services/Looting/CompositeLootGenerator.cs index 61eb11523..d89284adc 100644 --- a/src/Perpetuum/Services/Looting/CompositeLootGenerator.cs +++ b/src/Perpetuum/Services/Looting/CompositeLootGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Services.Looting { @@ -11,6 +12,12 @@ public CompositeLootGenerator(params ILootGenerator[] generators) _generators = generators; } + public IReadOnlyCollection GetInfos() + { + var infos = _generators.Select(x => x.GetInfos()); + return infos.SelectMany(infoset => infoset).ToList().AsReadOnly(); + } + public IEnumerable Generate() { var items = new List(); diff --git a/src/Perpetuum/Services/Looting/ILootGenerator.cs b/src/Perpetuum/Services/Looting/ILootGenerator.cs index 5d72452dd..65bfb2afc 100644 --- a/src/Perpetuum/Services/Looting/ILootGenerator.cs +++ b/src/Perpetuum/Services/Looting/ILootGenerator.cs @@ -5,5 +5,12 @@ namespace Perpetuum.Services.Looting public interface ILootGenerator { IEnumerable Generate(); + + IReadOnlyCollection GetInfos(); + } + + public interface ISplittableLootGenerator + { + List GetGenerators(int splitCount); } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Looting/LootContainer.cs b/src/Perpetuum/Services/Looting/LootContainer.cs index 6b3643edf..7195ddea9 100644 --- a/src/Perpetuum/Services/Looting/LootContainer.cs +++ b/src/Perpetuum/Services/Looting/LootContainer.cs @@ -325,7 +325,9 @@ protected override void OnRemovedFromZone(IZone zone) { _itemRepository.DeleteAll(this); zone.UnitService.RemoveUserUnit(this); - }).ContinueWith(t => { base.OnRemovedFromZone(zone); }); + }).ContinueWith(t => { + base.OnRemovedFromZone(zone); + }); } protected class LootContainerProgressInfoPacketBuilder : IBuilder diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 66c8d0bcf..65bb42489 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -1,17 +1,30 @@ +using Perpetuum.Items; +using System; using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Services.Looting { public class LootGenerator : ILootGenerator { - private readonly IEnumerable _lootInfos; + private readonly IEnumerable _lootInfos = new List(); public LootGenerator(IEnumerable lootInfos) { + if (lootInfos == null) + { + _lootInfos = new List(); + return; + } _lootInfos = lootInfos; } + public IReadOnlyCollection GetInfos() + { + return this._lootInfos.ToList(); + } + public IEnumerable Generate() { foreach (var info in _lootInfos) @@ -20,8 +33,73 @@ public IEnumerable Generate() continue; var lootItem = LootItemBuilder.Create(info.item).SetDamaged(info.damaged).Build(); + lootItem.Quantity = lootItem.ItemInfo.randomQuantity(); //roll random on generate yield return lootItem; } } } + + + public class SplittableLootGenerator : ISplittableLootGenerator + { + private readonly IReadOnlyCollection _lootInfos = new List(); + private readonly Random _random; + + public SplittableLootGenerator(ILootGenerator lootGenerator) + { + _lootInfos = lootGenerator.GetInfos(); + _random = new Random(); + } + + public List GetGenerators(int splitCount) + { + List generatorInfos = new List(); + + foreach (var info in _lootInfos) + { + var lootItem = LootItemBuilder.Create(info.item).SetDamaged(info.damaged).Build(); + lootItem.Quantity = lootItem.ItemInfo.randomQuantity(); + var quantity = lootItem.Quantity; + var splitQuantity = quantity / splitCount; + var remainder = quantity % splitCount; + var amounts = new int[splitCount]; + for (var i = 0; i < splitCount; i++) + { + var amount = splitQuantity + (remainder > 0 ? 1 : 0); + remainder--; + amounts[i] = amount; + } + amounts = amounts.OrderBy(x => _random.Next()).ToArray(); + for (var i = 0; i < splitCount; i++) + { + ItemInfo itemInfo = new ItemInfo(lootItem.ItemInfo.Definition, amounts[i], amounts[i]); + var generatorInfo = new LootGeneratorItemInfo(itemInfo, info.damaged, info.probability); + generatorInfos.Add(generatorInfo); + } + } + + int counter = 0; + List[] lootGeneratorItemInfos = new List[splitCount]; + foreach (var generatorInfo in generatorInfos) + { + if (lootGeneratorItemInfos[counter] == null) + { + lootGeneratorItemInfos[counter] = new List(); + } + lootGeneratorItemInfos[counter].Add(generatorInfo); + + counter++; + counter %= splitCount; + } + + List lootGenerators = new List(); + for (var i = 0; i < splitCount; i++) + { + lootGenerators.Add(new LootGenerator(lootGeneratorItemInfos[i])); + } + + return lootGenerators; + } + + } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Looting/LootItemBuilder.cs b/src/Perpetuum/Services/Looting/LootItemBuilder.cs index ae691af7a..bd0c4275b 100644 --- a/src/Perpetuum/Services/Looting/LootItemBuilder.cs +++ b/src/Perpetuum/Services/Looting/LootItemBuilder.cs @@ -22,7 +22,7 @@ public static LootItemBuilder Create(ItemInfo item) public static LootItemBuilder Create(int definition) { - return new LootItemBuilder(new ItemInfo(definition)); + return new LootItemBuilder(new ItemInfo(definition, 1)); } public static LootItemBuilder Create(Item item) diff --git a/src/Perpetuum/Services/Looting/LootService.cs b/src/Perpetuum/Services/Looting/LootService.cs index 1dc87eceb..7037323d6 100644 --- a/src/Perpetuum/Services/Looting/LootService.cs +++ b/src/Perpetuum/Services/Looting/LootService.cs @@ -39,8 +39,8 @@ public IEnumerable GetIntrusionLootInfos(Outpost outpost, foreach (var loot in loots) { - var item = new ItemInfo(loot.itemDefinition,FastRandom.NextInt(loot.quantity)); - yield return new LootGeneratorItemInfo(item,false,loot.probability); + var item = new ItemInfo(loot.itemDefinition, loot.quantity.Min, loot.quantity.Max); //roll random on init + yield return new LootGeneratorItemInfo(item, false, loot.probability); } } @@ -73,7 +73,7 @@ private static LootGeneratorItemInfo CreateNpcLootInfoFromRecord(IDataRecord rec var definition = record.GetValue(k.lootDefinition.ToLower()); var minq = record.GetValue("minquantity"); var maxq = record.GetValue(k.quantity); - var item = new ItemInfo(definition, FastRandom.NextInt(minq, maxq)) + var item = new ItemInfo(definition, minq, maxq) { IsRepackaged = record.GetValue(k.repackaged) }; diff --git a/src/Perpetuum/Services/Looting/NullLootGenerator.cs b/src/Perpetuum/Services/Looting/NullLootGenerator.cs index c3f0ae387..793d033f1 100644 --- a/src/Perpetuum/Services/Looting/NullLootGenerator.cs +++ b/src/Perpetuum/Services/Looting/NullLootGenerator.cs @@ -8,5 +8,10 @@ public IEnumerable Generate() { yield break; } + + public IReadOnlyCollection GetInfos() + { + return null; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Mail/MailHandler.cs b/src/Perpetuum/Services/Mail/MailHandler.cs index debc94828..3dbab2ffe 100644 --- a/src/Perpetuum/Services/Mail/MailHandler.cs +++ b/src/Perpetuum/Services/Mail/MailHandler.cs @@ -185,5 +185,39 @@ public static int NewMailCount(Character character) { return Db.Query().CommandText("newMailCount").SetParameter("@characterID", character.Id).ExecuteScalar(); } + + public static ErrorCodes SendWelcomeMailBeginTutorial(Character newPlayer) + { + return SendWelcomeMail(newPlayer, PreMadeMailNames.TUTORIAL_ARRIVE); + } + + public static ErrorCodes SendWelcomeMailExitTutorial(Character newPlayer) + { + return SendWelcomeMail(newPlayer, PreMadeMailNames.TUTORIAL_FINISH); + } + + private static ErrorCodes SendWelcomeMail(Character newPlayer, string mailName) + { + // TODO: query for this character once upon startup + Character sender = Character.GetByNick("[OPP] Sparky - The Syndicate Welcome Agent"); + + string subject, body; + using (var scope = Db.CreateTransaction()) + { + var records = Db.Query() + .CommandText("SELECT subject, body FROM premademail WHERE name = @mailName") + .SetParameter("@mailName", mailName) + .Execute(); + subject = records.First().GetValue("subject"); + body = records.First().GetValue("body"); + + subject = subject.Replace("$USER$", newPlayer.Nick); + body = body.Replace("$USER$", newPlayer.Nick); + + scope.Complete(); + } + + return SendMail(sender, newPlayer, subject, body, MailType.character, out _, out _); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Mail/PreMadeMailNames.cs b/src/Perpetuum/Services/Mail/PreMadeMailNames.cs new file mode 100644 index 000000000..74e80f5be --- /dev/null +++ b/src/Perpetuum/Services/Mail/PreMadeMailNames.cs @@ -0,0 +1,8 @@ +namespace Perpetuum.Services.Mail +{ + public static class PreMadeMailNames + { + public const string TUTORIAL_ARRIVE = "TutorialArriveMail"; + public const string TUTORIAL_FINISH = "TutorialCompleteMail"; + } +} diff --git a/src/Perpetuum/Services/MarketEngine/Market.cs b/src/Perpetuum/Services/MarketEngine/Market.cs index 56a060a34..943134fb5 100644 --- a/src/Perpetuum/Services/MarketEngine/Market.cs +++ b/src/Perpetuum/Services/MarketEngine/Market.cs @@ -97,7 +97,12 @@ public bool IsOnTrainingZone() public bool IsOnGammaZone() { - return GetDockingBase().IsOnGammaZone(); + return GetDockingBase()?.IsOnGammaZone() ?? false; + } + + public bool IsVisible(Character character) + { + return GetDockingBase()?.IsVisible(character) ?? false; } public bool IsPlayerControlledMarketTax() @@ -791,16 +796,77 @@ public void InsertVendorBuyOrder(int definition, double price, long vendorEid = .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); } - public void AddOtherStuffToGammaMarket() + public void InsertVendorSellOrder(int definition, double price, long vendorEid = 0) { - //5373 5375 - //def_ammo_mining_probe_energymineral_direction - //def_ammo_mining_probe_energymineral_tile - InsertVendorBuyOrder(5373,45); - InsertVendorBuyOrder(5375,45); + const string query = @"INSERT dbo.marketitems ( + marketeid, + itemdefinition, + submittereid, + duration, + isSell, + price, + quantity, + isvendoritem +) VALUES ( + @marketEid, + @definition, + @vendorEid, + 0, + 1, + @price, + -1, + 1 +) "; + Db.Query().CommandText(query) + .SetParameter("@marketEid", Eid) + .SetParameter("@price", price) + .SetParameter("@definition", definition) + .SetParameter("@vendorEid", vendorEid) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + } - //5376 def_gate_capsule - InsertVendorBuyOrder(5376, 250000); + public void AddOtherStuffToGammaMarket() + { + //229 898 899 900 901 902 5844 5375 5134 5137 tile charges + InsertVendorSellOrder(229,90); + InsertVendorSellOrder(898, 90); + InsertVendorSellOrder(899, 90); + InsertVendorSellOrder(900, 90); + InsertVendorSellOrder(901, 90); + InsertVendorSellOrder(902, 90); + InsertVendorSellOrder(5844, 270); + InsertVendorSellOrder(5375, 90); + InsertVendorSellOrder(5134, 90); + InsertVendorSellOrder(5137, 90); + + //5152 5153 5154 5155 5156 5157 5159 5160 5373 5845 direction charges + InsertVendorSellOrder(5152, 90); + InsertVendorSellOrder(5153, 90); + InsertVendorSellOrder(5154, 90); + InsertVendorSellOrder(5155, 90); + InsertVendorSellOrder(5156, 90); + InsertVendorSellOrder(5157, 90); + InsertVendorSellOrder(5159, 90); + InsertVendorSellOrder(5160, 90); + InsertVendorSellOrder(5373, 90); + InsertVendorSellOrder(5845, 270); + + //1147 universal + //2932 3253 3254 artifact charges + InsertVendorSellOrder(1147, 30); + InsertVendorSellOrder(2932, 600); + InsertVendorSellOrder(3253, 1800); + InsertVendorSellOrder(3254, 3000); + + //others + //1478 field container + //5163 concrete layer + //5188 syn-sec container + //4734 decon block + InsertVendorSellOrder(1478, 4011); + InsertVendorSellOrder(5163, 75000); + InsertVendorSellOrder(5188, 1000); + InsertVendorSellOrder(4734, 10000); } public long GetVendorEid() diff --git a/src/Perpetuum/Services/MarketEngine/MarketHandler.cs b/src/Perpetuum/Services/MarketEngine/MarketHandler.cs index e64d5ef12..cd4ddc778 100644 --- a/src/Perpetuum/Services/MarketEngine/MarketHandler.cs +++ b/src/Perpetuum/Services/MarketEngine/MarketHandler.cs @@ -2,6 +2,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.Caching; +using Perpetuum.Accounting.Characters; using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -24,6 +26,7 @@ public class MarketHandler /// private readonly ConcurrentDictionary _marketPriceCollectors = new ConcurrentDictionary(); private readonly ConcurrentDictionary _marketEidToDockingBaseEid = new ConcurrentDictionary(); + private readonly ObjectCache _visibleMarkets = new MemoryCache("visibleMarkets"); public MarketHandler(DockingBaseHelper dockingBaseHelper) { @@ -81,7 +84,17 @@ public IEnumerable GetAllDefaultMarketsEids(bool includeGamma = false, boo .Where(pc => includeGamma == pc.IsGammaMarket) .Select(pc => pc.Market.Eid); } - + + public IEnumerable GetAllVisibleMarketsFor(Character c) + { + return _visibleMarkets.Get(c.Eid.ToString(), () => + { + return _marketPriceCollectors.Values + .Where(pc => !pc.IsTrainingMarket) + .Where(pc => pc.IsVisible(c)) + .Select(pc => pc.Market.Eid); + }, TimeSpan.FromMinutes(2)); + } private long _trainingMarketEid = -1L; diff --git a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs index 6d78486ca..afd6c042f 100644 --- a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs +++ b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs @@ -19,7 +19,7 @@ public interface IMarketOrderRepository : IRepository MarketOrder GetHighestBuyOrder(int itemDefinition, double price, long submitterEid, Market market, long forMembersOf); IEnumerable GetByMarket(Market market); - IEnumerable GetByMarketEids(IEnumerable marketEids, int itemDefinition, long forMembersOf); + IEnumerable GetByMarketEids(IEnumerable marketEids, int itemDefinition, long? forMembersOf); IEnumerable GetByCharacter(Character character); IEnumerable GetByDefinition(int itemDefinition, long forMembersOf, Market market); IEnumerable GetExpiredOrders(TimeSpan duration); @@ -38,7 +38,7 @@ public static class MarketOrderRepositoryExtensions public class MarketOrderRepository : IMarketOrderRepository { - private const string ORDER_COLUMNS = "marketitemid, marketeid, itemeid, itemdefinition, submittereid, submitted, duration, isSell, price, quantity, usecorporationwallet, isvendoritem, formembersof FROM marketitems "; + private const string ORDER_COLUMNS = "marketitems.marketitemid, marketitems.marketeid, marketitems.itemeid, marketitems.itemdefinition, marketitems.submittereid, marketitems.submitted, marketitems.duration, marketitems.isSell, marketitems.price, marketitems.quantity, marketitems.usecorporationwallet, marketitems.isvendoritem, marketitems.formembersof FROM marketitems "; private const string ORDER_SELECT = "SELECT " + ORDER_COLUMNS; private const string TOP1_SELECT = "SELECT TOP (1) " + ORDER_COLUMNS; private readonly MarketOrder.Factory _marketOrderFactory; @@ -115,7 +115,7 @@ public IEnumerable GetByMarket(Market market) .ToArray(); } - public IEnumerable GetByMarketEids(IEnumerable marketEids, int itemDefinition, long forMembersOf) + public IEnumerable GetByMarketEids(IEnumerable marketEids, int itemDefinition, long? forMembersOf) { var marketEidsString = marketEids.ArrayToString(); @@ -155,7 +155,9 @@ public IEnumerable GetExpiredOrders(TimeSpan duration) public IEnumerable GetAllByDefinition(int definition) { - const string cmd = ORDER_SELECT + @"where itemdefinition = @definition"; + // this is an example as to why we should not have a table that references itself. + // the purpose of this is to not return market items on disabled zones. + const string cmd = ORDER_SELECT + @"INNER JOIN entities as childentity on (marketitems.marketeid=childentity.eid) INNER JOIN entities parent on (childentity.eid=parent.eid) INNER JOIN zoneentities on (parent.parent=zoneentities.eid) INNER JOIN zones on (zoneentities.zoneid=zones.id) where itemdefinition = @definition and zones.enabled=1"; return Db.Query().CommandText(cmd) .SetParameter("@definition", definition) diff --git a/src/Perpetuum/Services/MarketEngine/MarketPriceCollector.cs b/src/Perpetuum/Services/MarketEngine/MarketPriceCollector.cs index 75ec82dec..dc6967a72 100644 --- a/src/Perpetuum/Services/MarketEngine/MarketPriceCollector.cs +++ b/src/Perpetuum/Services/MarketEngine/MarketPriceCollector.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using Perpetuum.Accounting.Characters; using Perpetuum.EntityFramework; using Perpetuum.Log; @@ -28,6 +29,11 @@ public long GetBaseEid() public bool IsGammaMarket => Market.IsOnGammaZone(); + public bool IsVisible(Character character) + { + return Market.IsVisible(character); + } + /// /// Filters and inserts an average price entry /// diff --git a/src/Perpetuum/Services/MissionEngine/MissionDataCacheObjects/MissionDataCache.cs b/src/Perpetuum/Services/MissionEngine/MissionDataCacheObjects/MissionDataCache.cs index 81f86fb79..9f3b4af5f 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionDataCacheObjects/MissionDataCache.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionDataCacheObjects/MissionDataCache.cs @@ -579,7 +579,7 @@ private static Dictionary GetBonusMultiplierDictionary() /// - /// NOT gravel, gammaterial, energymineral + /// NOT gravel, gammaterial, energymineral, fluxore /// private void InitPossibleMinerals() { @@ -590,7 +590,7 @@ private void InitPossibleMinerals() SELECT m.definition FROM dbo.mineralconfigs mc JOIN dbo.minerals m ON m.idx = mc.materialtype WHERE mc.zoneid=@zoneId - AND mc.materialtype !=10 and mc.materialtype != 15 and mc.materialtype != 13 + AND mc.materialtype !=10 and mc.materialtype != 15 and mc.materialtype != 13 AND mc.materialtype != 16 "; foreach (var zone in _zoneManager.Zones) { diff --git a/src/Perpetuum/Services/MissionEngine/MissionProcessorObjects/MissionProcessorDeliverMission.cs b/src/Perpetuum/Services/MissionEngine/MissionProcessorObjects/MissionProcessorDeliverMission.cs index d91a9f676..6589d2101 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionProcessorObjects/MissionProcessorDeliverMission.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionProcessorObjects/MissionProcessorDeliverMission.cs @@ -384,8 +384,6 @@ public void DeliverMissionByRequest(Character character, int locationId = 0) { Logger.Info("++ Deliver targets begins. characterId:" + character.Id); - var deliveryState = DeliverResult.nothingHappened; - //get the affected characters from the gang var charactersToProcess = GetGangMembersCached(character); diff --git a/src/Perpetuum/Services/MissionEngine/MissionStructures/MissionLocation.cs b/src/Perpetuum/Services/MissionEngine/MissionStructures/MissionLocation.cs index 7d64f00a9..4b32cd347 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionStructures/MissionLocation.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionStructures/MissionLocation.cs @@ -225,6 +225,12 @@ 3 asia_great_corp */ public int GetRaceSpecificCoinDefinition() { + //TODO: Fixme: I am a hack for Syndicatification + if(this.zoneId==0 || this.zoneId == 8) //For New Virginia and Hershfield + { + return EntityDefault.GetByName(DefinitionNames.UNIVERSAL_MISSION_COIN).Definition; + } + //--end hack-- switch (RaceId) { case 1: diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index 5622a3e37..dc7230db7 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -67,15 +67,20 @@ private void PayAsMineral() } - public static readonly double[] PayOutMultipliers = new double[] { 25, 50, 100, 250, 500, 750, 1000, 1500, 2500, 4000 }; + //ECONOMY! TODO put this in DB! + public static readonly double[] PayOutMultipliers = new double[] { 100, 190, 280, 370, 460, 550, 640, 625, 730, 820 }; + private double GetRewardMultiplierByLevel(int level) + { + return PayOutMultipliers[level.Clamp(0, PayOutMultipliers.Length - 1)]; + } private void PayReward() { var level = _missionInProgress.MissionLevel; - var pMult = PayOutMultipliers[level]; + var pMult = GetRewardMultiplierByLevel(level); var reward = Target.Reward; _reward = reward * pMult; - + Log("est:" + _estimation + " lvl:" + level + " bRwe:" + reward + " pMult:" + pMult + " SUM:" + _reward + " for " + Target); } @@ -89,8 +94,8 @@ private void PayAsArtifact() var quantity = _targetInProgress.myTarget.Quantity; var level = _missionInProgress.MissionLevel; var reward = Target.Reward; - var pMult = PayOutMultipliers[level]; - + var pMult = GetRewardMultiplierByLevel(level); + dangerFee = pMult * quantity * reward; Log("artifact danger fee " + _reward + " for " + Target); @@ -113,7 +118,7 @@ private void PayQuantityAndReward() var quantity = GetQuantityOrProgress; var level = _missionInProgress.MissionLevel; var reward = Target.Reward; - var pMult = PayOutMultipliers[level]; + var pMult = GetRewardMultiplierByLevel(level); _reward = pMult * quantity * reward; diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs index 82bf7152e..da2419c0d 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs @@ -467,8 +467,8 @@ protected override void ProcessMyQuantity(MissionInProgress missionInProgress) base.ProcessMyQuantity(missionInProgress); } - - private readonly int[] _artifactRanges = new[] {15, 20, 30, 50, 80, 130, 200, 300, 400, 500}; + // TODO: to DB? + private readonly int[] _artifactRanges = new[] {25, 50, 75, 100, 150, 225, 350, 525, 750, 1000}; private void ScaleArtifactRange(MissionInProgress missionInProgress) { @@ -706,7 +706,7 @@ public override void PostLoadedAsConfigTarget() Logger.Error("target must have 10 level multipliers " + this); } - private readonly double[] _levelMultipliers = new double[] { 5, 10, 25, 50, 75, 100, 150, 200, 300, 500 }; + private readonly double[] _levelMultipliers = new double[] { 5, 10, 25, 50, 80, 125, 200, 300, 450, 650 }; public override double GetLevelMultiplier(MissionInProgress missionInProgress) { var level = missionInProgress.MissionLevel; @@ -773,7 +773,7 @@ public override void PostLoadedAsConfigTarget() base.PostLoadedAsConfigTarget(); } - private readonly double[] _levelMultipliers = new double[] { 5, 10, 25, 50, 75, 100, 150, 200, 300, 500 }; + private readonly double[] _levelMultipliers = new double[] { 5, 10, 25, 50, 75, 115, 175, 250, 425, 600 }; public override double GetLevelMultiplier(MissionInProgress missionInProgress) { var level = missionInProgress.MissionLevel; diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/ZoneMissionTarget.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/ZoneMissionTarget.cs index 308584a05..524b776fd 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/ZoneMissionTarget.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/ZoneMissionTarget.cs @@ -374,7 +374,7 @@ protected void SpawnNpcOnSuccess(IPresenceManager presenceManager,Position posit }).LogExceptions(); } - public const int RANDOM_POP_NPC_LIFETIME_MINUTES = 15; + public const int RANDOM_POP_NPC_LIFETIME_MINUTES = 25; private DirectPresence AddDirectPresenceToPosition(IPresenceManager presenceManager,Position successPosition) { diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index 0823ddadc..78e3b2c7e 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -403,9 +403,11 @@ private void ChooseNpcRace() { var races = new[] {1, 2, 3}; - var factionChance = FastRandom.NextDouble(); - - if (factionChance > 0.25) + if (myLocation.ZoneConfig.IsAlpha) //TODO: Fixme -- Alpha as neutral-assumption + { + _selectedRace = races.RandomElement(); + } + else if (FastRandom.NextDouble() > 0.25) { _selectedRace = myLocation.RaceId; } @@ -1349,15 +1351,45 @@ public IList SpawnRewardItems(out MissionLocation locationUsed) } //or spawn - var rewardItem = publicContainer.CreateAndAddItem(reward.ItemInfo, false, item => { item.Owner = character.Eid; }); - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.MissionRewardTake) - .SetCharacter(character) - .SetItem(rewardItem) - .SetContainer(publicContainer); - character.LogTransaction(b); + //If item is tokens, split amoung participants + if (myLocation.GetRaceSpecificCoinDefinition() == reward.ItemInfo.Definition) + { + var participants = GetParticipants(); + if (participants.Count > 0 && myLocation.GetRaceSpecificCoinDefinition() == reward.ItemInfo.Definition) + { + var tokenSplitQuantity = (int)Math.Ceiling((double)reward.ItemInfo.Quantity / (double)participants.Count); + var splitTokens = new MissionReward(new ItemInfo(reward.ItemInfo.Definition, tokenSplitQuantity)); + Item tokenItem = null; + foreach (var participant in participants) + { + var rewardItem = publicContainer.CreateAndAddItem(splitTokens.ItemInfo, false, item => { item.Owner = participant.Eid; }); + + var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.MissionRewardTake) + .SetCharacter(character) + .SetItem(rewardItem) + .SetContainer(publicContainer); + character.LogTransaction(b); + tokenItem = rewardItem; + } + if (tokenItem != null) + { + spawnedItems.Add(tokenItem); + } + } + } + else + { + var rewardItem = publicContainer.CreateAndAddItem(reward.ItemInfo, false, item => { item.Owner = character.Eid; }); + + var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.MissionRewardTake) + .SetCharacter(character) + .SetItem(rewardItem) + .SetContainer(publicContainer); + character.LogTransaction(b); - spawnedItems.Add(rewardItem); + spawnedItems.Add(rewardItem); + } } } @@ -1428,6 +1460,25 @@ public double GetDifficultyReward } } + public double GetParticipantBonusModifier() + { + return ComputeParticipantBonusMultiplier(GetParticipants().Count); + } + + private double ComputeParticipantBonusMultiplier(int participantCount) + { + //Solo or squad of 1 and initial estimate + if (participantCount < 2) + { + return 1.0; + } + //Modify total reward by participants + //Clamp participant count to [1,MaxmimalGangParticipants] + double participantBonus = 0.05; //TODO expose parameter in DB + participantCount = participantCount.Clamp(1, MaxmimalGangParticipants); + double participantModifier = 1 + participantCount * participantBonus; + return participantModifier; + } /// /// Both old and new tech handled @@ -1444,6 +1495,8 @@ public void PayOutMission(double rewardFraction, List participants, L double zoneFactor; GetFinalReward(out rewardSum, out distanceReward, out difficultyReward, out rewardByTargets, out riskCompensation, out zoneFactor); + rewardSum *= GetParticipantBonusModifier(); + paymentData.Add("totalReward", Math.Round(rewardSum)); paymentData.Add("distanceReward", distanceReward); paymentData.Add("difficultyReward", difficultyReward); @@ -1516,11 +1569,12 @@ public void PayOutMission(double rewardFraction, List participants, L } } + double amountToCharacter; //if the complete reward is paid to the corporation if (Math.Abs(taxRatio - 1.0) < double.Epsilon) - continue; - - var amountToCharacter = Math.Round((1 - taxRatio)*realRewardFeePerCharacter); + amountToCharacter = 0d; + else + amountToCharacter = Math.Round((1 - taxRatio)*realRewardFeePerCharacter); oneCharacterPayOut.Add(k.fee, amountToCharacter); paymentsPerCharacter.Add("p" + count++, oneCharacterPayOut); @@ -2047,16 +2101,13 @@ public List CollectActiveCPRGDefinitions() public const int MISSION_SUCCESS_TELEPORT_MINUTES = 5; - public void EnableTeleportOnSuccess(IEnumerable characters, int zoneId) { var zone = _zoneManager.GetZone(myLocation.ZoneConfig.Id); - if (zone.Configuration.IsBeta) - { - //on beta, only the owner - zone.GetPlayer(character)?.EnableSelfTeleport(TimeSpan.FromMinutes(MISSION_SUCCESS_TELEPORT_MINUTES),zoneId); + + // Not an option on beta/gamma + if (!zone.Configuration.Protected) return; - } foreach (var currentCharacter in characters) { @@ -2274,7 +2325,7 @@ public int CalculateEp() { if (myLocation.ZoneConfig.Type == ZoneType.Training) return 0; var missionLevel = MissionLevel; - if (missionLevel == 0) return 0; //level 0 missions ---> no ep + if (missionLevel == 0) return 1; //Change: level 0 -> 1ep if (missionLevel == -1) { missionLevel = 1; } //10 training missions var result = myMission.Targets.Count * Math.Pow(missionLevel, 0.5); diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs index f55ebe287..7a5728625 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Perpetuum.Groups.Corporations; @@ -68,7 +69,7 @@ private void CalculateEstimation( ) private double ZoneFactor { - get { return _missionInProgress.myLocation.ZoneConfig.IsAlpha ? 1 : 3; } + get { return _missionInProgress.myLocation.ZoneConfig.IsAlpha ? 1 : 2; } //TODO: Beta multiplier! } @@ -152,18 +153,20 @@ public override void VisitRandomMission(RandomMission randomMission) var standingValue = 1 / (diffmult * rawGrindLevel); + standingValue *= _missionInProgress.GetParticipantBonusModifier(); + // 0 -> 0.0 10 - > 1.0 // 6 -> 0.6 --> 0.5*0.6 => positive x 30% - - var f = _missionInProgress.MissionLevel.Clamp(0, 9) / 9.0 ; - - var negativeValue = -1 * (standingValue / 2) * f; //20% extension can reduce it to 10% - var positiveAllianceEid = _missionLocation.Agent.OwnerAlliance.Eid; - var opposingAllianceEid = DefaultCorporationDataCache.SelectOpposingAlliance(positiveAllianceEid); + //Implementation: Add reputation for all factions equally -- No factional bias! + IEnumerable allianceEids = DefaultCorporationDataCache.GetMegaCorporationEids(); + List standingChangeForSyndicatification = new List(); + foreach(long id in allianceEids) + { + standingChangeForSyndicatification.Add(new MissionStandingChange(id, standingValue)); + } + _standingChanges = standingChangeForSyndicatification.ToArray(); - _standingChanges = new[] {new MissionStandingChange(positiveAllianceEid, standingValue), new MissionStandingChange(opposingAllianceEid, negativeValue) }; - } } @@ -192,7 +195,10 @@ public override void VisitRandomMission(RandomMission randomMission) //var coinQuantity = (int)Math.Round(Math.Pow(1.3 + level, 2.5 / 3.0) * randomMission.DifficultyMultiplier); var coinQuantity = RandomMission.CoinQuantity(level, randomMission.DifficultyMultiplier); - + + //Total token quantity increase + coinQuantity = (int) Math.Round(_missionInProgress.GetParticipantBonusModifier() * coinQuantity); + var coinItemInfo = new ItemInfo(coinDefinition, coinQuantity); var coinReward = new MissionReward(coinItemInfo); diff --git a/src/Perpetuum/Services/MissionEngine/Missions/RandomMission.cs b/src/Perpetuum/Services/MissionEngine/Missions/RandomMission.cs index d4d762346..f26cb7710 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/RandomMission.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/RandomMission.cs @@ -64,9 +64,9 @@ public override string ToString() return info + " RND"; } - + //ECONOMY: TODO - put this in DB // 0 1 2 3 4 5 6 7 8 9 - private static readonly int[] CoinsPerLevel = { 10, 20, 40, 70, 120, 200, 310, 450, 620, 800}; + private static readonly int[] CoinsPerLevel = { 10, 20, 40, 70, 120, 200, 300, 400, 500, 600}; public static int CoinQuantity(int missionLevel, double difficultyMultiplier) { /* diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/InsuraceFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/InsuraceFacility.cs index 483587cce..0efe2f0ad 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/InsuraceFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/InsuraceFacility.cs @@ -102,6 +102,7 @@ public void InsuranceBuy(Character character, Robot robot, ref int currentInsura robot.Initialize(character); robot.CheckOwnerCharacterAndCorporationAndThrowIfFailed(character); + corporation = character.GetCorporation(); long? corporationEid = character.CorporationEid; var useCorporationWallet = false; diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs index 87516bc45..be5b3cc6b 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs @@ -29,8 +29,8 @@ public override Dictionary GetFacilityInfo(Character character) infoDict.Add(k.percentageMaterial, GetPercentageFromAdditiveComponent(additiveComponentMaterial)); infoDict.Add(k.percentageTime, GetPercentageFromAdditiveComponent(additiveComponentTime)); - infoDict.Add(k.materialExtensionPoints, (int) GetMaterialExtensionBonus(character)); - infoDict.Add(k.timeExtensionPoints, (int) GetTimeExtensionBonus(character)); + infoDict.Add(k.materialExtensionPoints, (int)GetMaterialExtensionBonus(character)); + infoDict.Add(k.timeExtensionPoints, (int)GetTimeExtensionBonus(character)); infoDict.Add(k.cycle, 1); infoDict.Add(k.rounds, GetMaxRounds(character)); @@ -42,7 +42,7 @@ public override ProductionFacilityType FacilityType get { return ProductionFacilityType.MassProduce; } } - public static Mill CreateWithRandomEID(string definitionName) + public static Mill CreateWithRandomEID(string definitionName) { var mill = (Mill)Factory.CreateWithRandomEID(definitionName); mill.CreateSystemStorage(); @@ -67,13 +67,13 @@ public override int RealMaxSlotsPerCharacter(Character character) // double extensionBonus = GetSlotExtensionBonus(character); - return 1 + (int) (extensionBonus * (1 + standingRatio)); + return 1 + (int)(extensionBonus * (1 + standingRatio)); } public override int GetSlotExtensionBonus(Character character) { - return (int) character.GetExtensionsBonusSummary(ExtensionNames.PRODUCTION_MAX_MILL_SLOTS_BASIC, ExtensionNames.PRODUCTION_MAX_MILL_SLOTS_ADVANCED, ExtensionNames.PRODUCTION_MAX_MILL_SLOTS_EXPERT); + return (int)character.GetExtensionsBonusSummary(ExtensionNames.PRODUCTION_MAX_MILL_SLOTS_BASIC, ExtensionNames.PRODUCTION_MAX_MILL_SLOTS_ADVANCED, ExtensionNames.PRODUCTION_MAX_MILL_SLOTS_EXPERT); } public override double GetMaterialExtensionBonus(Character character) @@ -91,20 +91,20 @@ public override double GetTimeExtensionBonus(Character character) private int GetAdditiveComponentForMaterial(Character character) { var extensionPoint = GetMaterialExtensionBonus(character); - var standingPoints = GetStandingOfOwnerToCharacter(character) * 20; + var standingPoints = GetStandingPoints(character); var facilityPoints = GetFacilityPoint(); - return (int) (facilityPoints + standingPoints + extensionPoint); + return (int)(facilityPoints + standingPoints + extensionPoint); } private int GetAdditiveComponentForTime(Character character) { var extensionComponent = GetTimeExtensionBonus(character); - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var facilityPoints = GetFacilityPoint(); - return (int) (facilityPoints + extensionComponent + standingComponent); + return (int)(facilityPoints + extensionComponent + standingComponent); } @@ -145,7 +145,7 @@ public int CalculateFinalProductionTimeSeconds(Character character, int targetDe var facilityProductionTime = GetProductionTimeSeconds(); - return (int) (facilityProductionTime * multiplier * durationModifier); + return (int)(facilityProductionTime * multiplier * durationModifier); } public IDictionary CalibrateLine(Character character, long calibrationEid, Container container) @@ -155,18 +155,18 @@ public IDictionary CalibrateLine(Character character, long calib lineCount.ThrowIfGreaterOrEqual(maxSlots, ErrorCodes.MaximumAmountOfProducionsReached); - var calibrationItem = (CalibrationProgram) container.GetItemOrThrow(calibrationEid); + var calibrationItem = (CalibrationProgram)container.GetItemOrThrow(calibrationEid); calibrationItem.Quantity.ThrowIfNotEqual(1, ErrorCodes.ServerError); var targetDefinition = calibrationItem.TargetDefinition; targetDefinition.ThrowIfEqual(0, ErrorCodes.CPRGNotProducible); var targetDefault = EntityDefault.Get(targetDefinition); - + calibrationItem.HasComponents.ThrowIfFalse(ErrorCodes.CPRGNotProducible); - + //no mission stuff here - if (calibrationItem.IsMissionRelated || targetDefault.CategoryFlags.IsCategory(CategoryFlags.cf_random_items) ) + if (calibrationItem.IsMissionRelated || targetDefault.CategoryFlags.IsCategory(CategoryFlags.cf_random_items)) { if (this.GetDockingBase().IsOnGammaZone()) { @@ -243,7 +243,7 @@ public ProductionInProgress LineStart(Character character, ProductionLine produc //so it can ask less that in the mission but never more var preMatMult = materialMultiplier; materialMultiplier = materialMultiplier.Clamp(); - + Logger.Info("pre material multiplier:" + preMatMult + " -> " + materialMultiplier); } @@ -273,7 +273,7 @@ public ProductionInProgress LineStart(Character character, ProductionLine produc newProduction.resultDefinition = productionLine.TargetDefinition; newProduction.totalProductionTimeSeconds = productionTimeSeconds; newProduction.baseEID = Parent; - newProduction.pricePerSecond = cprg.IsMissionRelated ? 0.0 : GetPricePerSecond(); + newProduction.pricePerSecond = cprg.IsMissionRelated ? 0.0 : GetPricePerSecond(productionLine.TargetDefinition); newProduction.ReservedEids = reservedEids; newProduction.amountOfCycles = cycles; newProduction.useCorporationWallet = useCorporationWallet; @@ -339,8 +339,8 @@ public Dictionary QueryMaterialAndTime(CalibrationProgram calibr ProductionLine.GetDecalibratedEfficiencies(lineOrCPRGMaterialPoints, lineOrCPRGTimePoints, decalibration.decrease, ref newMaterialEfficiency, ref newTimeEfficiency); - lineOrCPRGMaterialPoints = (int) newMaterialEfficiency; - lineOrCPRGTimePoints = (int) newTimeEfficiency; + lineOrCPRGMaterialPoints = (int)newMaterialEfficiency; + lineOrCPRGTimePoints = (int)newTimeEfficiency; } bool hasBonus; @@ -350,23 +350,22 @@ public Dictionary QueryMaterialAndTime(CalibrationProgram calibr { materialMultiplier = materialMultiplier.Clamp(); //never ask more than what we have in the mission } - + var materials = ProductionDescription.GetRequiredComponentsInfo(ProductionInProgressType.massProduction, 1, materialMultiplier, calibrationProgram.Components); materials.Count.ThrowIfEqual(0, ErrorCodes.WTFErrorMedicalAttentionSuggested); var productionTimeSeconds = CalculateFinalProductionTimeSeconds(character, targetDefintion, lineOrCPRGTimePoints); - var price = CalculateProductionPrice(productionTimeSeconds); + var price = CalculateProductionPrice(productionTimeSeconds, targetDefintion); if (calibrationProgram.IsMissionRelated) { //mission stuff is fixed - price = 0; + price = 0; productionTimeSeconds = 10; } - result.Add(k.materials, materials); result.Add(k.productionTime, productionTimeSeconds); result.Add(k.price, price); @@ -378,9 +377,9 @@ public Dictionary QueryMaterialAndTime(CalibrationProgram calibr } - private long CalculateProductionPrice(int productionTime) + private long CalculateProductionPrice(int productionTime, int targetDefinition) { - return (long) (productionTime * GetPricePerSecond()); + return (long)(productionTime * GetPricePerSecond(targetDefinition)); } public override IDictionary EndProduction(ProductionInProgress productionInProgress, bool forced) @@ -407,7 +406,7 @@ private IDictionary EndMassProduction(ProductionInProgress produ var outputDefinition = productionInProgress.resultDefinition; //load container - var container = (PublicContainer) Container.GetOrThrow(PublicContainerEid); + var container = (PublicContainer)Container.GetOrThrow(PublicContainerEid); container.ReloadItems(productionInProgress.character); @@ -455,9 +454,9 @@ private IDictionary EndMassProduction(ProductionInProgress produ { //set it from the ct resultItem.Quantity = randomCalibrationProgram.TargetQuantity; - Logger.Info("mission quantity is forced from CPRG:" + randomCalibrationProgram.Eid + " qty:" + randomCalibrationProgram.TargetQuantity); + Logger.Info("mission quantity is forced from CPRG:" + randomCalibrationProgram.Eid + " qty:" + randomCalibrationProgram.TargetQuantity); } - + } container.Save(); @@ -474,7 +473,7 @@ private IDictionary EndMassProduction(ProductionInProgress produ {k.lineCount, linesList.Count} }; - ProductionProcessor.EnqueueProductionMissionTarget(MissionTargetType.massproduce, productionInProgress.character, MyMissionLocationId(), resultItem.Definition, resultItem.Quantity); + ProductionProcessor.EnqueueProductionMissionTarget(MissionTargetType.massproduce, productionInProgress.character, MyMissionLocationId(), resultItem.Definition, resultItem.Quantity); return replyDict; } @@ -540,7 +539,7 @@ public void TryNextRound(Character character, int productionLineId, int cycles, err = gex.error; } - character.CreateErrorMessage(Commands.ProductionLineStart,err).Send(); + character.CreateErrorMessage(Commands.ProductionLineStart, err).Send(); Logger.Info("production next round start failed: " + err + " characterId:" + character.Id + " lineId:" + productionLineId + " facility:" + this); @@ -592,12 +591,12 @@ private ProductionLine DecalibrateLine(ProductionInProgress productionInProgress //this CPRG drives the production cprg = productionLine.GetOrCreateCalibrationProgram(this); - + // mission mechanism if (cprg.IsMissionRelated) { //kill the line, delete cprg - + wasLineDead = true; ProductionLine.DeleteById(productionLine.Id); @@ -648,7 +647,7 @@ private ProductionLine DecalibrateLine(ProductionInProgress productionInProgress return productionLine; } - + public static int GetMaxRounds(Character character) @@ -660,7 +659,7 @@ public static int GetMaxRounds(Character character) return 1; } - return (int) character.GetExtensionBonusByName(ExtensionNames.LONGTERM_PRODUCTION); + return (int)character.GetExtensionBonusByName(ExtensionNames.LONGTERM_PRODUCTION); } public override void OnRemoveFromGame() @@ -669,7 +668,7 @@ public override void OnRemoveFromGame() Logger.Info(linesDeleted + " production lines were deleted from " + this); - var q = + var q = @"DELETE dbo.runningproductionreserveditem WHERE runningid in (SELECT id FROM dbo.runningproduction WHERE facilityEID=@facilityEid); DELETE dbo.runningproduction WHERE facilityEID=@facilityEid"; diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs index f9d93b114..8895a41c5 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs @@ -14,6 +14,7 @@ using Perpetuum.Services.Standing; using Perpetuum.Units.DockingBases; using Perpetuum.Zones; +using Perpetuum.Zones.Intrusion; namespace Perpetuum.Services.ProductionEngine.Facilities { @@ -35,12 +36,25 @@ protected ProductionFacility() public MissionDataCache MissionDataCache { get; set; } public DockingBaseHelper DockingBaseHelper { get; set; } public ProductionInProgress.Factory ProductionInProgressFactory { get; set; } - public RobotHelper RobotHelper { protected get; set; } + public RobotHelper RobotHelper { protected get; set; } public override string ToString() { return $"{ED.Name} {ED.Definition} {Eid}"; } + + private int GetFacilityBonus() + { + int modifier = 0; + var dockingbase = GetDockingBase(); + if (dockingbase is Outpost) + { + ProductionFacility facility = (dockingbase as Outpost).GetProductionFacilities().Where(x => x.Eid == this.Eid).First(); + int extrapts = facility.DynamicProperties.GetOrDefault(k.extrapoints); //Entity-property for unique facility base-proficiencies + modifier = (Outpost.GetFacilityLevelFromStack(facility.Eid) + extrapts) * 25; //bonus per facility level + } + return modifier; + } public virtual Dictionary GetFacilityInfo(Character character) { @@ -137,7 +151,7 @@ private bool LoadIsOnTrainingZone() protected virtual int GetFacilityPoint() { - return ED.Options.Points; + return ED.Options.Points + GetFacilityBonus(); } protected int GetPercentageFromAdditiveComponent(int additiveComponent) @@ -204,9 +218,22 @@ public virtual bool IsOpen } } - private int GetStandingPoints(Character character) + protected int GetStandingPoints(Character character) + { + var standing = GetStandingOfOwnerToCharacter(character); + if (standing < 0.0) + { + standing = 0.0; + } + + return (int)(standing * 20); + } + + //Overload method for price/sec + public virtual double GetPricePerSecond(int targetDefinition) { - return (int)( GetStandingOfOwnerToCharacter(character)*20); + var mod = ProductionDataAccess.GetProductionPriceModifier(targetDefinition); + return GetPricePerSecond() * mod.Clamp(1, 10); } public virtual double GetPricePerSecond() @@ -234,7 +261,7 @@ protected void CreateSystemStorage() protected void RemoveStorage() { - Repository.DeleteTree(StorageEid); + Repository.DeleteTreeButNotRoot(StorageEid); } protected void MoveItemsToStorage(params Item[] itemsToReserve) diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs index 7798ad241..34151e596 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs @@ -53,7 +53,10 @@ public override void OnDeleteFromDb() public override int GetSlotExtensionBonus(Character character) { - return (int)character.GetExtensionsBonusSummary(ExtensionNames.PRODUCTION_MAX_PROTOTYPER_SLOTS_BASIC); + return (int)character.GetExtensionsBonusSummary( + ExtensionNames.PRODUCTION_MAX_PROTOTYPER_SLOTS_BASIC, + ExtensionNames.PRODUCTION_MAX_PROTOTYPER_SLOTS_ADVANCED, + ExtensionNames.PRODUCTION_MAX_PROTOTYPER_SLOTS_EXPERT); } public override int RealMaxSlotsPerCharacter(Character character) @@ -116,7 +119,7 @@ public ProductionInProgress StartPrototype(Character character, ProductionDescri productionInProgress.character = character; productionInProgress.facilityEID = Eid; productionInProgress.finishTime = DateTime.Now.AddSeconds(prototypeTimeSeconds); - productionInProgress.pricePerSecond = GetPricePerSecond(); + productionInProgress.pricePerSecond = GetPricePerSecond(productionDescription.GetPrototypeDefinition()); productionInProgress.ReservedEids = reservedEids; productionInProgress.resultDefinition = productionDescription.GetPrototypeDefinition(); productionInProgress.startTime = DateTime.Now; @@ -160,16 +163,16 @@ public int CalculatePrototypeTimeSeconds(Character character, int targetDefiniti } - public long CalculatePrototypePrice(int prototypeTime) + public long CalculatePrototypePrice(int prototypeTime, int targetDefinition) { - return (int) (GetPricePerSecond()*prototypeTime); + return (int) (GetPricePerSecond(targetDefinition) * prototypeTime); } private int GetAdditiveComponentForMaterial(Character character) { var extensionComponent = GetMaterialExtensionBonus(character); - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var facilityPoints = GetFacilityPoint(); return (int) (extensionComponent + facilityPoints + standingComponent); @@ -178,7 +181,7 @@ private int GetAdditiveComponentForMaterial(Character character) private int GetAdditiveComponentForTime(Character character) { var extensionComponent = GetTimeExtensionBonus(character); - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var facilityPoints = GetFacilityPoint(); return (int)(extensionComponent + facilityPoints + standingComponent); diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Refinery.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Refinery.cs index b15d36fcf..f1964a2de 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Refinery.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Refinery.cs @@ -84,7 +84,7 @@ private int GetAdditiveComponent(Character character) { var extensionPoints = GetMaterialExtensionBonus(character); var refinerypoints = GetFacilityPoint(); - var standingPoints = GetStandingOfOwnerToCharacter(character) * 20; + var standingPoints = GetStandingPoints(character); return (int)( extensionPoints + refinerypoints + standingPoints); } diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Repair.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Repair.cs index 209d0f811..1de39dd2d 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Repair.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Repair.cs @@ -33,11 +33,7 @@ public override ProductionFacilityType FacilityType private int GetAdditiveComponent(Character character) { - var standing = GetStandingOfOwnerToCharacter(character); - if (standing < 0.0) - standing = 0.0; - - var standingComponent = standing * 20; + var standingComponent = GetStandingPoints(character); var extensionComponent = GetMaterialExtensionBonus(character); var facilityPoints = GetFacilityPoint(); diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Reprocessor.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Reprocessor.cs index 78a081fe1..d1687f3b0 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Reprocessor.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Reprocessor.cs @@ -39,7 +39,7 @@ public override ProductionFacilityType FacilityType private int GetAdditiveComponent(Character character) { var extensionBonus = GetMaterialExtensionBonus(character); - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var facilityComponent = GetFacilityPoint(); return (int) (standingComponent + extensionBonus + facilityComponent); diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs index 0fc1767ed..9703594f5 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs @@ -50,7 +50,7 @@ private int GetAdditiveComponent(Character character) { var basePoints = GetFacilityPoint(); var extensionPoints = GetMaterialExtensionBonus(character); - var standingPoints = GetStandingOfOwnerToCharacter(character) * 20; + var standingPoints = GetStandingPoints(character); return (int) (basePoints + extensionPoints + standingPoints); } @@ -59,7 +59,7 @@ private int GetAdditiveComponentForTime(Character character) { var basePoints = GetFacilityPoint(); var extensionPoints = GetTimeExtensionBonus(character); - var standingPoints = GetStandingOfOwnerToCharacter(character) * 20; + var standingPoints = GetStandingPoints(character); return (int)(basePoints + extensionPoints + standingPoints); } @@ -68,7 +68,7 @@ private int GetAdditiveComponentForTimeWithLevelDifference(Character character, { var basePoints = GetFacilityPoint(); var extensionPoints = GetTimeExtensionBonus(character); - var standingPoints = GetStandingOfOwnerToCharacter(character) * 20; + var standingPoints = GetStandingPoints(character); var levelDifferencePoints = levelDifference * 5; return (int)(basePoints + extensionPoints + standingPoints + levelDifferencePoints ); @@ -191,7 +191,7 @@ public ErrorCodes StartResearch(Character character, int researchTimeSeconds, It newProduction.resultDefinition = cprgDefiniton; newProduction.totalProductionTimeSeconds = researchTimeSeconds; newProduction.baseEID = Parent; - newProduction.pricePerSecond = GetPricePerSecond(); + newProduction.pricePerSecond = GetPricePerSecond(sourceItem.Definition); newProduction.ReservedEids = (from i in itemsList select i.Eid).ToArray(); newProduction.useCorporationWallet = useCorporationWallet; newProduction.amountOfCycles = 1; @@ -403,7 +403,7 @@ public IDictionary ResearchQuery(Character character, int resear researchTimeSeconds = GetShortenedProductionTime(researchTimeSeconds); - var price = missionRelated ? 0.0 : researchTimeSeconds * GetPricePerSecond(); + var price = missionRelated ? 0.0 : researchTimeSeconds * GetPricePerSecond(targetDefinition); ProductionDataAccess.GetCalibrationDefault(outputDefinition, out int materialEfficiency, out int timeEfficiency); @@ -421,7 +421,7 @@ public IDictionary ResearchQuery(Character character, int resear //calculate nominal var nominalResearchTimeSeconds = CalculateNominalResearchTimeSeconds(character, itemResearchLevel.researchLevel); - var nominalPrice = missionRelated ? 0.0 : nominalResearchTimeSeconds * GetPricePerSecond(); + var nominalPrice = missionRelated ? 0.0 : nominalResearchTimeSeconds * GetPricePerSecond(targetDefinition); ProductionDataAccess.GetCalibrationDefault(outputDefinition, out int nominalMaterialEfficiency, out int nominalTimeEfficiency); diff --git a/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs b/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs index 7a9ccb06c..b7e273ba6 100644 --- a/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs +++ b/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs @@ -11,6 +11,7 @@ public interface IProductionDataAccess IDictionary ResearchLevels { get; } ILookup ProductionComponents { get; } IDictionary ProductionDurations { get; } + IDictionary ProductionCost { get; } IDictionary CalibrationDefaults { get; } ProductionDecalibration GetDecalibration(int targetDefinition); diff --git a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs index 03a14e151..123e23dfd 100644 --- a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs +++ b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs @@ -423,7 +423,7 @@ protected override void InitConnected() private int GetAdditiveComponent(Character character) { - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var extensionComponent = GetMaterialExtensionBonus(character); var facilityComponent = GetFacilityPoint(); @@ -674,6 +674,9 @@ public ProductionInProgress StartCPRGForge(Character character, long sourceEid, sourceCalibration.ED.CategoryFlags.IsCategory(CategoryFlags.cf_random_calibration_programs).ThrowIfTrue(ErrorCodes.MissionItemCantBeForged); targetCalibration.ED.CategoryFlags.IsCategory(CategoryFlags.cf_random_calibration_programs).ThrowIfTrue(ErrorCodes.MissionItemCantBeForged); + //OPP: prevent combination of Shop-provided CTs - will inflate number of runs + sourceCalibration.ED.CategoryFlags.IsCategory(CategoryFlags.cf_dynamic_cprg).ThrowIfTrue(ErrorCodes.MissionItemCantBeForged); + targetCalibration.ED.CategoryFlags.IsCategory(CategoryFlags.cf_dynamic_cprg).ThrowIfTrue(ErrorCodes.MissionItemCantBeForged); sourceCalibration.CheckTargetForForgeAndThrowIfFailed(targetCalibration); @@ -699,7 +702,7 @@ public ProductionInProgress StartCPRGForge(Character character, long sourceEid, productionInProgress.character = character; productionInProgress.facilityEID = Eid; productionInProgress.finishTime = DateTime.Now.AddSeconds(forgeTimeSeconds); - productionInProgress.pricePerSecond = GetPricePerSecond(); + productionInProgress.pricePerSecond = GetPricePerSecond(targetDefinition); productionInProgress.ReservedEids = reservedEids; productionInProgress.resultDefinition = targetDefinition; productionInProgress.startTime = DateTime.Now; @@ -726,7 +729,7 @@ public ProductionInProgress StartCPRGForge(Character character, long sourceEid, private int GetAdditiveComponentForTime(Character character) { - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var extensionComponent = GetMaterialExtensionBonus(character); var facilityPoints = GetFacilityPoint(); @@ -823,7 +826,7 @@ protected override int GetFacilityPoint() private int GetAdditiveComponentForPoints(Character character) { var extensionComponent = GetPointExtensionBonus(character); - var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; + var standingComponent = GetStandingPoints(character); var facilityPoints = GetFacilityPoint(); return (int) (extensionComponent + standingComponent + facilityPoints); diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs b/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs new file mode 100644 index 000000000..93d25613b --- /dev/null +++ b/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.ProductionEngine +{ + public class ProductionCost + { + public long? categoryFlag; + public int? tierType; + public int? tierLevel; + public double costModifier; + } + + public interface IProductionCostReader + { + IEnumerable ProductionCost { get; } + ProductionCost GetProductionCostByED(EntityDefault ed); + double GetProductionCostModByED(EntityDefault ed); + } + + public class ProductionCostReader : IProductionCostReader + { + private const double MIN = 1.0; + private const double MAX = 10.0; + + public IEnumerable ProductionCost + { + get { return _costTable.Values; } + } + private readonly IDictionary _costTable; + public ProductionCostReader() + { + _costTable = Database.CreateCache("productioncost", "id", r => + { + var cost = new ProductionCost + { + categoryFlag = r.GetValue(k.category), + tierType = r.GetValue(k.tierType), + tierLevel = r.GetValue(k.tierLevel), + costModifier = r.GetValue("costmodifier") + }; + return cost; + }); + } + + /// + /// Map an EntityDefault to a ProductionCost based on matching CategoryFlags, tierType, tierLevel. + /// Tiebreaker logic is weighted such that a match on Category > TierLevel > TierType + /// And where a match on TierLevel and Type will not outweigh Category + /// + /// EntityDefault + /// ProductionCost + public ProductionCost GetProductionCostByED(EntityDefault ed) + { + var matchScores = ProductionCost.GroupBy(c => + (((CategoryFlags)(c.categoryFlag ?? 0) == ed.CategoryFlags) ? 5 : 0) + + (((TierType)(c.tierType ?? 0) == ed.Tier.type) ? 1 : 0) + + (((c.tierLevel ?? 0) == ed.Tier.level) ? 3 : 0)); + + var bestMatchScore = matchScores.Max(x => x.Key); + return matchScores.FirstOrDefault(x => x.Key == bestMatchScore).Select(g => g).FirstOrDefault(); + } + + public double GetProductionCostModByED(EntityDefault ed) + { + var prodCost = GetProductionCostByED(ed) ?? (new ProductionCost { costModifier = MIN }); + return prodCost.costModifier.Clamp(MIN, MAX); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs index c6bee1950..497e812af 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs @@ -18,16 +18,18 @@ public class ProductionDataAccess : IProductionDataAccess private IDictionary _productionDurations; private IDictionary _calibrationDefaults; private IDictionary _productionDecalibrations; + private IProductionCostReader _productionCostReader; - public ProductionDataAccess(IEntityDefaultReader entityDefaultReader) + public ProductionDataAccess(IEntityDefaultReader entityDefaultReader, IProductionCostReader costReader) { _entityDefaultReader = entityDefaultReader; + _productionCostReader = costReader; } public void Init() { - _prototypes = Database.CreateCache("prototypes", "definition", "prototype"); - _researchlevels = Database.CreateCache("itemresearchlevels", "definition", r => + _prototypes = Database.CreateCache("prototypes", k.definition, "prototype"); + _researchlevels = Database.CreateCache("itemresearchlevels", k.definition, r => { var level = new ItemResearchLevel { @@ -36,24 +38,41 @@ public void Init() calibrationProgramDefinition = r.GetValue(k.calibrationProgram.ToLower()) }; return level; - },ItemResearchLevelFilter); + }, ItemResearchLevelFilter); - _productionComponents = Database.CreateLookupCache("components", "definition", r => + _productionComponents = Database.CreateLookupCache("components", k.definition, r => { var ed = _entityDefaultReader.Get(r.GetValue(k.componentDefinition.ToLower())); var amount = r.GetValue(k.componentAmount.ToLower()); return new ProductionComponent(ed, amount); }, r => _entityDefaultReader.Exists(r.GetValue(k.definition))); - _productionDurations = Database.CreateCache("productionduration", "category", "durationmodifier"); - _calibrationDefaults = Database.CreateCache("calibrationdefaults", "definition", r => new CalibrationDefault(r)); + _productionDurations = Database.CreateCache("productionduration", k.category, "durationmodifier"); + _calibrationDefaults = Database.CreateCache("calibrationdefaults", k.definition, r => new CalibrationDefault(r)); _productionDecalibrations = Database.CreateCache("productiondecalibration", "categoryflag", r => { var distorsionMin = r.GetValue(k.distorsionMin.ToLower()); var distorsionMax = r.GetValue(k.distorsionMax.ToLower()); var decrease = r.GetValue("decrease"); - return new ProductionDecalibration(distorsionMin,distorsionMax,decrease); + return new ProductionDecalibration(distorsionMin, distorsionMax, decrease); }); + + _researchlevels = Database.CreateCache("itemresearchlevels", k.definition, r => + { + var level = new ItemResearchLevel + { + definition = r.GetValue(k.definition), + researchLevel = r.GetValue(k.researchLevel.ToLower()), + calibrationProgramDefinition = r.GetValue(k.calibrationProgram.ToLower()) + }; + return level; + }, ItemResearchLevelFilter); + + ProductionCost = new Dictionary(); + foreach (var ed in EntityDefault.All) + { + ProductionCost.Add(ed.Definition, _productionCostReader.GetProductionCostModByED(ed)); + } } public bool ItemResearchLevelFilter(IDataRecord record) @@ -77,13 +96,12 @@ public bool ItemResearchLevelFilter(IDataRecord record) return false; } - - public IDictionary Prototypes => _prototypes; public IDictionary ResearchLevels => _researchlevels; public ILookup ProductionComponents => _productionComponents; public IDictionary ProductionDurations => _productionDurations; public IDictionary CalibrationDefaults => _calibrationDefaults; + public IDictionary ProductionCost { get; private set; } public ProductionDecalibration GetDecalibration(int targetDefinition) { diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs index 82c32d2bc..89c2bd621 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs @@ -7,25 +7,25 @@ namespace Perpetuum.Services.ProductionEngine { public static class ProductionDataAccessExtensions { - public static int GetPrototypePair(this IProductionDataAccess dataAccess,int definition) + public static int GetPrototypePair(this IProductionDataAccess dataAccess, int definition) { return !dataAccess.Prototypes.ContainsKey(definition) ? definition : dataAccess.Prototypes[definition]; } - public static int GetOriginalDefinitionFromPrototype(this IProductionDataAccess dataAccess,int prototypeDefinition) + public static int GetOriginalDefinitionFromPrototype(this IProductionDataAccess dataAccess, int prototypeDefinition) { var x = dataAccess.Prototypes.FirstOrDefault(p => p.Value == prototypeDefinition).Key; return x == 0 ? prototypeDefinition : x; } - public static bool IsPrototypeDefinition(this IProductionDataAccess dataAccess,int definition) + public static bool IsPrototypeDefinition(this IProductionDataAccess dataAccess, int definition) { var res = dataAccess.Prototypes.Any(t => t.Value == definition); Logger.Info("input is prototype: " + res); return res; } - public static int GetResultingDefinitionFromCalibrationDefinition(this IProductionDataAccess dataAccess,int definition) + public static int GetResultingDefinitionFromCalibrationDefinition(this IProductionDataAccess dataAccess, int definition) { var target = (dataAccess.ResearchLevels.Values.Where(ir => ir.calibrationProgramDefinition == definition).Select(ir => ir.definition)).FirstOrDefault(); if (target == 0) @@ -36,12 +36,12 @@ public static int GetResultingDefinitionFromCalibrationDefinition(this IProducti return target; } - public static ItemResearchLevel GetItemReserchLevelByCalibrationProgram(this IProductionDataAccess dataAccess,CalibrationProgram calibrationProgram) + public static ItemResearchLevel GetItemReserchLevelByCalibrationProgram(this IProductionDataAccess dataAccess, CalibrationProgram calibrationProgram) { return dataAccess.ResearchLevels.Values.FirstOrDefault(i => i.calibrationProgramDefinition == calibrationProgram.Definition); } - public static bool IsItemResearchable(this IProductionDataAccess dataAccess,int definition) + public static bool IsItemResearchable(this IProductionDataAccess dataAccess, int definition) { return dataAccess.ResearchLevels.ContainsKey(definition); } @@ -51,7 +51,7 @@ public static int GetResearchLevel(this IProductionDataAccess dataAccess, int de return dataAccess.ResearchLevels.TryGetValue(definition, out ItemResearchLevel researchLevel) ? researchLevel.researchLevel : 0; } - public static double GetProductionDuration(this IProductionDataAccess dataAccess,int targetDefinition) + public static double GetProductionDuration(this IProductionDataAccess dataAccess, int targetDefinition) { if (!EntityDefault.TryGet(targetDefinition, out EntityDefault ed)) { @@ -71,12 +71,34 @@ public static double GetProductionDuration(this IProductionDataAccess dataAccess return 1.0; } - public static void GetCalibrationDefault(this IProductionDataAccess dataAccess,EntityDefault ed, out int materialEfficiency, out int timeEfficiency) + /// + /// Applies EntityDefault specific cost modifiers based on tech level/type and category + /// + /// + /// + /// factor to multiply cost + public static double GetProductionPriceModifier(this IProductionDataAccess dataAccess, int targetDefinition) { - dataAccess.GetCalibrationDefault(ed.Definition,out materialEfficiency,out timeEfficiency); + var modifier = 1.0; + if (!EntityDefault.TryGet(targetDefinition, out EntityDefault ed)) + { + Logger.Error("definition was not found: " + targetDefinition); + return modifier; + } + + if (dataAccess.ProductionCost.TryGetValue(ed.Definition, out double value)) + { + modifier = value; + } + return modifier; + } + + public static void GetCalibrationDefault(this IProductionDataAccess dataAccess, EntityDefault ed, out int materialEfficiency, out int timeEfficiency) + { + dataAccess.GetCalibrationDefault(ed.Definition, out materialEfficiency, out timeEfficiency); } - public static void GetCalibrationDefault(this IProductionDataAccess dataAccess,int definition, out int materialEfficiency, out int timeEfficiency) + public static void GetCalibrationDefault(this IProductionDataAccess dataAccess, int definition, out int materialEfficiency, out int timeEfficiency) { if (dataAccess.CalibrationDefaults.TryGetValue(definition, out CalibrationDefault calibrationDefault)) { diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionInProgress.cs b/src/Perpetuum/Services/ProductionEngine/ProductionInProgress.cs index b30998209..01a996a31 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionInProgress.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionInProgress.cs @@ -11,6 +11,7 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Items; +using Perpetuum.Items.Helpers; using Perpetuum.Log; using Perpetuum.Services.ProductionEngine.Facilities; using Perpetuum.Units.DockingBases; diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs b/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs index 3e959e9ff..4d5c74ff1 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs @@ -7,6 +7,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Items.Helpers; using Perpetuum.Log; using Perpetuum.Services.ProductionEngine.CalibrationPrograms; using Perpetuum.Services.ProductionEngine.Facilities; diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs b/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs index d1cf2cdbc..109373333 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs @@ -278,22 +278,23 @@ private void EndProduction(ProductionInProgress productionInProgress, bool force private int CalculateEp(ProductionFacility facility, ProductionInProgress productionInProgress) { - - - var ep = Math.Ceiling(productionInProgress.TotalProductionTime.TotalHours ); - - // dev cheat - if (productionInProgress.character.AccessLevel.IsAdminOrGm()) - { - ep = Math.Ceiling(productionInProgress.TotalProductionTime.TotalSeconds); - } - var dockingBase = facility.GetDockingBase(); if (dockingBase.Zone is TrainingZone) + { return 0; - + } + + var ep = productionInProgress.TotalProductionTime.TotalHours; if (dockingBase.Zone.Configuration.IsBeta) + { ep *= BETA_EP_MULTIPLIER; + } + + //For <1hr jobs, give EP at a rate that would equate to 1 per hour + if (ep < 1 && FastRandom.NextDouble() < ep) + { + ep = 1; + } return (int) ep; } @@ -849,7 +850,7 @@ public ErrorCodes PrototypeQuery(Character character, int targetDefinition, Prot var facilityInfo = prototyper.GetFacilityInfo(character); var prototypeTimeSeconds = prototyper.CalculatePrototypeTimeSeconds(character, targetDefinition); - var price = prototyper.CalculatePrototypePrice(prototypeTimeSeconds); + var price = prototyper.CalculatePrototypePrice(prototypeTimeSeconds, targetDefinition); bool hasBonus; var materialMultiplier = prototyper.CalculateMaterialMultiplier(character, targetDefinition, out hasBonus); var materials = ProductionDescription.GetRequiredComponentsInfo( ProductionInProgressType.prototype, 1, materialMultiplier, productionDescription.Components.ToList()); diff --git a/src/Perpetuum/Services/Relay/ILoginQueueService.cs b/src/Perpetuum/Services/Relay/ILoginQueueService.cs index 01d7b6309..4091acda1 100644 --- a/src/Perpetuum/Services/Relay/ILoginQueueService.cs +++ b/src/Perpetuum/Services/Relay/ILoginQueueService.cs @@ -5,6 +5,6 @@ namespace Perpetuum.Services.Relay { public interface ILoginQueueService : IProcess { - void EnqueueAccount(ISession session, int accountID, string hwHash); + void EnqueueAccount(ISession session, int accountID, string hwHash, int language); } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Relay/IRelayStateService.cs b/src/Perpetuum/Services/Relay/IRelayStateService.cs index 2af4aabf0..89ea0ec38 100644 --- a/src/Perpetuum/Services/Relay/IRelayStateService.cs +++ b/src/Perpetuum/Services/Relay/IRelayStateService.cs @@ -8,5 +8,6 @@ public interface IRelayStateService RelayState State { get; set; } event Action StateChanged; void SendStateToClient(ISession session); + void ConfigOnlyAllowAdmins(bool enabled); } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Relay/LoginQueueService.cs b/src/Perpetuum/Services/Relay/LoginQueueService.cs index df0a72b9c..39f078b71 100644 --- a/src/Perpetuum/Services/Relay/LoginQueueService.cs +++ b/src/Perpetuum/Services/Relay/LoginQueueService.cs @@ -24,12 +24,14 @@ private class SignInInfo public readonly ISession session; public readonly int accountId; public readonly string hwHash; + public readonly int language; - public SignInInfo(ISession session, int accountId, string hwHash) + public SignInInfo(ISession session, int accountId, string hwHash, int language) { this.accountId = accountId; this.session = session; this.hwHash = hwHash.IsNullOrEmpty() ? "hash_" + FastRandom.NextString(7) : hwHash.Substring(0, Math.Min(50, hwHash.Length)); + this.language = language; } } @@ -46,9 +48,9 @@ public void Update(TimeSpan time) _timer.Update(time).IsPassed(RefreshQueue); } - public void EnqueueAccount(ISession session, int accountID, string hwHash) + public void EnqueueAccount(ISession session, int accountID, string hwHash, int language) { - var info = new SignInInfo(session,accountID,hwHash); + var info = new SignInInfo(session, accountID, hwHash, language); lock (_queueSync) { @@ -90,7 +92,7 @@ private void DequeueAccountInfo() try { - info.session.SignIn(info.accountId,info.hwHash); + info.session.SignIn(info.accountId, info.hwHash, info.language); } catch (Exception ex) { diff --git a/src/Perpetuum/Services/Relay/RelayInfoBuilder.cs b/src/Perpetuum/Services/Relay/RelayInfoBuilder.cs index eb1a13878..9a7d32193 100644 --- a/src/Perpetuum/Services/Relay/RelayInfoBuilder.cs +++ b/src/Perpetuum/Services/Relay/RelayInfoBuilder.cs @@ -41,6 +41,11 @@ public RelayInfoBuilder(GlobalConfiguration globalConfiguration, IRelayStateServ public RelayInfo Build() { + if (_globalConfiguration.StartServerInAdminOnlyMode) + { + _relayStateService.State = RelayState.OpenForAdminsOnly; + } + var info = new RelayInfo { state = _relayStateService.State, @@ -48,7 +53,13 @@ public RelayInfo Build() usersCount = _sessionManager.Sessions.Count(), maxUsers = _sessionManager.MaxSessions }; + return info; } + + public void ConfigOnlyAllowAdmins(bool enabled) + { + _globalConfiguration.StartServerInAdminOnlyMode = enabled; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Relay/RelayStateService.cs b/src/Perpetuum/Services/Relay/RelayStateService.cs index 0c11b869c..82e7dcb83 100644 --- a/src/Perpetuum/Services/Relay/RelayStateService.cs +++ b/src/Perpetuum/Services/Relay/RelayStateService.cs @@ -51,5 +51,11 @@ private MessageBuilder CreateStateMessageBuilder() var info = builder.Build(); return Message.Builder.SetCommand(Commands.State).WithData(info.ToDictionary()); } + + public void ConfigOnlyAllowAdmins(bool enabled) + { + var factory = _relayInfoBuilderFactory(); + factory.ConfigOnlyAllowAdmins(enabled); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Relay/ServerStateInfo.cs b/src/Perpetuum/Services/Relay/ServerStateInfo.cs index e9a909f1d..49e5a181b 100644 --- a/src/Perpetuum/Services/Relay/ServerStateInfo.cs +++ b/src/Perpetuum/Services/Relay/ServerStateInfo.cs @@ -73,7 +73,7 @@ public ServerInfo GetServerInfo() Contact = record.GetValue("contact"), IsOpen = record.GetValue("isOpen"), IsBroadcast = record.GetValue("isBroadcast"), - UsersCount = _sessionManager.Sessions.Count() + UsersCount = _sessionManager.Sessions.Count(c => c.IsAuthenticated) }; return serverInfo; diff --git a/src/Perpetuum/Services/Relics/RelicInfo.cs b/src/Perpetuum/Services/Relics/RelicInfo.cs new file mode 100644 index 000000000..64cb8e1a4 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicInfo.cs @@ -0,0 +1,105 @@ +using Perpetuum.Data; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Perpetuum.Services.Relics +{ + public class RelicInfo + { + public static RelicInfo CreateRelicInfoFromRecord(IDataRecord record) + { + var id = record.GetValue("id"); + var name = record.GetValue("name"); + var raceid = record.GetValue("raceid"); + var level = record.GetValue("level"); + var ep = record.GetValue("ep"); + var info = new RelicInfo(id, name, raceid, level, ep); + + return info; + } + + public static RelicInfo GetByIDFromDB(int id) + { + var relicinfos = Db.Query().CommandText("SELECT TOP 1 id, name, raceid, level, ep FROM relictypes WHERE id = @relicInfoId") + .SetParameter("@relicInfoId", id) + .Execute() + .Select(CreateRelicInfoFromRecord); + + return relicinfos.SingleOrDefault(); + } + + public static RelicInfo GetByNameFromDB(string name) + { + var relicinfos = Db.Query().CommandText("SELECT TOP 1 id, name, raceid, level, ep FROM relictypes WHERE name = @name") + .SetParameter("@name", name) + .Execute() + .Select(CreateRelicInfoFromRecord); + + return relicinfos.SingleOrDefault(); + } + + private int _id; + private string _name; + private int? _raceid; + private int? _level; + private int? _ep; + private Position _staticRelicPosistion; + public bool HasStaticPosistion = false; + + public RelicInfo(int id, string name, int? raceid, int? level, int? ep) + { + _id = id; + _name = name; + _ep = ep; + _raceid = raceid; + _level = level; + } + + public int GetLevel() + { + return this._level ?? 0; + } + + public int GetFaction() + { + return this._raceid ?? 0; + } + + public void SetPosition(Position p) + { + HasStaticPosistion = true; + _staticRelicPosistion = p; + } + + public Position GetPosition() + { + return _staticRelicPosistion; + } + + public int GetEP() + { + return this._ep ?? 5; + } + + public int GetID() + { + return this._id; + } + + public Dictionary ToDictionary() + { + var dictionary = new Dictionary + { + {k.name, this._name }, + {k.raceID, this.GetFaction()}, + {k.level, this.GetLevel()}, + {k.extensionPoints, this.GetEP()}, + {"isStatic", HasStaticPosistion}, + }; + + return dictionary; + } + + } +} diff --git a/src/Perpetuum/Services/Relics/RelicLoot.cs b/src/Perpetuum/Services/Relics/RelicLoot.cs new file mode 100644 index 000000000..c45936920 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicLoot.cs @@ -0,0 +1,125 @@ +using Perpetuum; +using Perpetuum.Builders; +using Perpetuum.Data; +using Perpetuum.Services.Looting; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Perpetuum.Services.Relics +{ + + public class RelicLootGenerator + { + private readonly RelicLootReader _relicLootRepository; + private Random _random; + + public RelicLootGenerator() + { + _relicLootRepository = new RelicLootReader(); + _random = new Random(); + } + + //Guard loot-loop from empty or low probability loot tables (would indicate bad/missing Relicloot entries) + private bool HasValidLoots(IRelicLoot[] loots) + { + return loots.Length > 0 && (0.1 < loots.Sum(loot => loot.Chance)); + } + + public RelicLootItems GenerateLoot(RelicInfo relicInfo) + { + var result = new List(); + + var loots = _relicLootRepository.GetRelicLoots(relicInfo).ToArray(); + + if (!HasValidLoots(loots)) + return null; + + do + { + foreach (var loot in loots) + { + var chance = _random.NextDouble(); + if (chance > loot.Chance) + continue; + + var builder = loot.GetLootItemBuilder(); + var lootItem = builder.Build(); + result.Add(lootItem); + } + + } while (!result.Any()); + + return new RelicLootItems(result); + } + } + + + public class RelicLootItems + { + public IEnumerable LootItems { get; private set; } + + public RelicLootItems(IEnumerable lootItems) + { + LootItems = lootItems; + } + } + + + public interface IRelicLoot + { + double Chance { get; } + IBuilder GetLootItemBuilder(); + } + + /// + /// Describes one loot item can be found in a discovered relic + /// + public class RelicLoot : IRelicLoot + { + private int Definition { get; set; } + private IntRange Quantity { get; set; } + public double Chance { get; private set; } + + public IBuilder GetLootItemBuilder() + { + return LootItemBuilder.Create(Definition) + .SetQuantity(FastRandom.NextInt(Quantity)) + .SetRepackaged(Packed); + } + + private bool Packed { get; set; } + private int RelicInfoId { get; set; } + + public RelicLoot(IDataRecord record) + { + Definition = record.GetValue("definition"); + Quantity = new IntRange(record.GetValue("minquantity"), record.GetValue("maxquantity")); + Chance = (float)record.GetValue("chance"); + Packed = record.GetValue("packed"); + RelicInfoId = record.GetValue("relictypeid"); + + } + + } + + + public class RelicLootReader + { + protected IRelicLoot CreateRelicLootFromRecord(IDataRecord record) + { + return new RelicLoot(record); + } + + public IEnumerable GetRelicLoots(RelicInfo info) + { + var loots = Db.Query().CommandText("SELECT definition,minquantity,maxquantity,chance,relictypeid,packed FROM relicloot WHERE relictypeid = @relicInfoId") + .SetParameter("@relicInfoId", info.GetID()) + .Execute() + .Select(CreateRelicLootFromRecord); + + return loots.ToList(); + } + } +} diff --git a/src/Perpetuum/Services/Relics/RelicManagers/AbstractRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/AbstractRelicManager.cs new file mode 100644 index 000000000..922a83d0c --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicManagers/AbstractRelicManager.cs @@ -0,0 +1,225 @@ +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; +using Perpetuum.Log; +using System.Threading; +using Perpetuum.Threading; + +namespace Perpetuum.Services.Relics +{ + public abstract class AbstractRelicManager : IRelicManager + { + //Constants + private const double ACTIVATION_RANGE = 3; //30m + private const double RESPAWN_PROXIMITY = 10.0 * ACTIVATION_RANGE; + private readonly TimeSpan THREAD_TIMEOUT = TimeSpan.FromSeconds(4); + + + protected abstract IZone Zone { get; } + protected abstract ReaderWriterLockSlim Lock { get; } + + protected int _max_relics = 0; + protected List _relics; + + private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); + + //DB-accessing objects + protected RelicLootGenerator relicLootGenerator; + + //Timers for update + private TimeSpan _refreshElapsed; + private TimeSpan _respawnElapsed; + protected TimeSpan _respawnRandomized; + + //Interface implementation + + public virtual void Start() + { + //Inject max relics on first start + while (GetRelicCount() < _max_relics) + { + SpawnRelic(); + } + } + + public virtual void Stop() + { + //TODO cleanup if using DB to cache relics + } + + public virtual void Update(TimeSpan time) + { + //Minimum tick rate + _refreshElapsed += time; + if (_refreshElapsed < _relicRefreshRate) + return; + + //Update Relic lifespans, refresh beams and remove dead relics + UpdateRelics(); + + _respawnElapsed += _refreshElapsed; + _refreshElapsed = TimeSpan.Zero; + + //check if time to spawn a new Relic + if (_respawnElapsed > _respawnRandomized) + { + if (GetRelicCount() < _max_relics) + { + SpawnRelic(); + _respawnRandomized = RollNextSpawnTime(); + } + _respawnElapsed = TimeSpan.Zero; + } + } + + public bool ForceSpawnRelicAt(int x, int y) + { + bool success = false; + try + { + var info = GetNextRelicType(); + if (info == null) + { + return false; + } + Position position = new Position(x, y); + AddRelicToZone(info, position); + success = true; + } + catch (Exception e) + { + Logger.Warning("Failed to spawn Relic by ForceSpawnRelicAt()"); + Logger.Warning(e.Message); + } + return success; + } + + public List> GetRelicListDictionary() + { + using (Lock.Read(THREAD_TIMEOUT)) + return DoGetRelicListDictionary(); + } + + protected virtual List> DoGetRelicListDictionary() + { + return _relics.Select(r => r.ToDebugDictionary()).ToList(); + } + + + + //Abstract methods for extension of behaviour' + protected abstract void RefreshBeam(IRelic relic); + + protected abstract Point FindRelicPosition(RelicInfo info); + + protected abstract RelicInfo GetNextRelicType(); + + protected abstract TimeSpan RollNextSpawnTime(); + + protected virtual IRelic MakeRelic(RelicInfo info, Position position) + { + return AbstractRelic.BuildAndAddToZone(info, Zone, position, relicLootGenerator.GenerateLoot(info)); + } + + //Common methods for all RelicManagers + protected int GetRelicCount() + { + using (Lock.Read(THREAD_TIMEOUT)) + return _relics.Count; + } + + private void SpawnRelic() + { + //Get Next Relictype based on the distribution of their probabilities on this zone + var maxAttempts = 100; + var attempts = 0; + RelicInfo info = null; + while (info == null) + { + info = GetNextRelicType(); + if (info.HasStaticPosistion && IsSpawnTooClose(info.GetPosition())) //The selected Relic type is static! We must check if another relic is in this location + { + info = null; + } + attempts++; + if (attempts > maxAttempts) + { + Logger.Error("Could not get RelicInfo for next Relic on Zone: " + Zone.Id); + return; + } + } + + attempts = 0; + Point pt = FindRelicPosition(info); + while (IsSpawnTooClose(pt) || !IsValidPos(pt)) + { + pt = FindRelicPosition(info); + attempts++; + if (attempts > maxAttempts) + { + Logger.Error("Could not get Position for next Relic on Zone: " + Zone.Id); + return; + } + } + AddRelicToZone(info, pt.ToPosition()); + } + + private bool IsValidPos(Point pt) + { + return Zone.IsWalkable(pt); + } + + private void AddRelicToZone(RelicInfo info, Position position) + { + using (Lock.Write(THREAD_TIMEOUT)) + { + var r = MakeRelic(info, position); + if (r != null) + { + _relics.Add(r); + } + } + } + + private bool IsSpawnTooClose(Point point) + { + using (Lock.Read(THREAD_TIMEOUT)) + { + foreach (var r in _relics) + { + if (RESPAWN_PROXIMITY > point.Distance(r.GetPosition())) + { + return true; + } + } + } + return false; + } + + private void UpdateRelics() + { + using (Lock.Write(THREAD_TIMEOUT)) + { + foreach (var r in _relics) + { + if (!r.IsAlive()) + { + r.RemoveFromZone(); + } + } + _relics.RemoveAll(r => !r.IsAlive()); + } + using (Lock.Read(THREAD_TIMEOUT)) + { + foreach (var r in _relics) + { + RefreshBeam(r); + } + } + } + + + } +} diff --git a/src/Perpetuum/Services/Relics/RelicManagers/IRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/IRelicManager.cs new file mode 100644 index 000000000..9fe393129 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicManagers/IRelicManager.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + + +namespace Perpetuum.Services.Relics +{ + /// + /// Specification interface for RelicManagers + /// + public interface IRelicManager + { + void Start(); + void Stop(); + bool ForceSpawnRelicAt(int x, int y); + List> GetRelicListDictionary(); + void Update(TimeSpan time); + } +} diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs new file mode 100644 index 000000000..2fd17e9f8 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -0,0 +1,115 @@ +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Drawing; +using Perpetuum.ExportedTypes; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.Intrusion; +using Perpetuum.Zones.Finders.PositionFinders; +using System.Threading; + +namespace Perpetuum.Services.Relics +{ + public class OutpostRelicManager : AbstractRelicManager + { + //Spawn time params + private readonly TimeSpan RESPAWN_RANDOM_WINDOW = TimeSpan.FromHours(1); + private readonly TimeSpan _respawnRate = TimeSpan.FromHours(4); + + private readonly Outpost _outpost; + private readonly Random _random; + private readonly RelicInfo _sapRelicInfo; + + //Beam Draw refresh + private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); + + // Spawn range and area + private const int SPAWN_MINIMUM_OUTPOST_DISTANCE = 90; + private const int SPAWN_MAXIMUM_OUTPOST_DISTANCE = 350; + private const int SPAWN_AREA_REQUIRED_SIZE = 3000; + + private IZone _zone; + protected override IZone Zone + { + get + { + return _zone; + } + } + private ReaderWriterLockSlim _lock; + protected override ReaderWriterLockSlim Lock + { + get + { + return _lock; + } + } + + public OutpostRelicManager(Outpost outpost) + { + _lock = new ReaderWriterLockSlim(); + _outpost = outpost; + _random = new Random(); + _relics = new List(); + _zone = outpost.Zone; + relicLootGenerator = new RelicLootGenerator(); + _max_relics = 1; + _respawnRandomized = RollNextSpawnTime(); + _sapRelicInfo = RelicInfo.GetByNameFromDB("sap_relic_basetype"); + } + + protected override IRelic MakeRelic(RelicInfo info, Position position) + { + return SAPRelic.BuildAndAddToZone(info, _zone, position, relicLootGenerator.GenerateLoot(info), _outpost); + } + + protected override TimeSpan RollNextSpawnTime() + { + var randomFactor = _random.NextDouble() - 0.5; + var minutesToAdd = RESPAWN_RANDOM_WINDOW.TotalMinutes * randomFactor; + return _respawnRate.Add(TimeSpan.FromMinutes(minutesToAdd)); + } + + protected override RelicInfo GetNextRelicType() + { + return _sapRelicInfo; + } + + protected override Point FindRelicPosition(RelicInfo info) + { + for(int i = 0; i < 10; i++) + { + var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D( + SPAWN_MINIMUM_OUTPOST_DISTANCE, SPAWN_MAXIMUM_OUTPOST_DISTANCE); + var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); + + posFinder.Find(out Position p); + var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); + if(result != null) + { + return p; + } + } + return Point.Empty; + } + + protected override void RefreshBeam(IRelic relic) + { + var position = relic.GetPosition(); + var p = _zone.FixZ(position); + var beamBuilder = Beam.NewBuilder().WithType(BeamType.sap_scanner_beam).WithTargetPosition(position) + .WithState(BeamState.AlignToTerrain) + .WithDuration(60); + _zone.CreateBeam(beamBuilder); + beamBuilder = Beam.NewBuilder().WithType(BeamType.nature_effect).WithTargetPosition(position) + .WithState(BeamState.AlignToTerrain) + .WithDuration(_relicRefreshRate); + _zone.CreateBeam(beamBuilder); + beamBuilder = Beam.NewBuilder().WithType(BeamType.green_20sec).WithTargetPosition(p.AddToZ(10)) + .WithState(BeamState.Hit) + .WithDuration(_relicRefreshRate); + _zone.CreateBeam(beamBuilder); + + } + } +} diff --git a/src/Perpetuum/Services/Relics/RelicManagers/ZoneRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/ZoneRelicManager.cs new file mode 100644 index 000000000..cf01545c4 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicManagers/ZoneRelicManager.cs @@ -0,0 +1,210 @@ +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Services.RiftSystem; +using System.Drawing; +using Perpetuum.ExportedTypes; +using Perpetuum.Zones.Beams; +using System.Threading; +using Perpetuum.Zones.Intrusion; + +namespace Perpetuum.Services.Relics +{ + public class ZoneRelicManager : AbstractRelicManager + { + //Spawn time params + private readonly TimeSpan RESPAWN_RANDOM_WINDOW = TimeSpan.FromHours(1); + private readonly TimeSpan _respawnRate = TimeSpan.FromHours(1.5); + + private Random _random; + + private IEnumerable _spawnInfos; + + //Beam Draw refresh + private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); + + //DB-accessing objects + private readonly RelicZoneConfigRepository relicZoneConfigRepository; + private readonly RelicSpawnInfoRepository relicSpawnInfoRepository; + + //Child RelicManagers + private IList outpostRelicManagers = new List(); + + private RiftSpawnPositionFinder _spawnPosFinder; + + private IZone _zone; + protected override IZone Zone + { + get + { + return _zone; + } + } + private ReaderWriterLockSlim _lock; + protected override ReaderWriterLockSlim Lock + { + get + { + return _lock; + } + } + + public ZoneRelicManager(IZone zone) + { + _lock = new ReaderWriterLockSlim(); + _random = new Random(); + _relics = new List(); + _zone = zone; + _spawnPosFinder = new PveRiftSpawnPositionFinder(zone); + if (zone.Configuration.Terraformable) + { + _spawnPosFinder = new PvpRiftSpawnPositionFinder(zone); + } + // init repositories and extract data + relicZoneConfigRepository = new RelicZoneConfigRepository(zone); + relicSpawnInfoRepository = new RelicSpawnInfoRepository(zone); + relicLootGenerator = new RelicLootGenerator(); + + //Get Zone Relic-Configuration data + var config = relicZoneConfigRepository.GetZoneConfig(); + _max_relics = config.GetMax(); + _respawnRate = config.GetTimeSpan(); + _respawnRandomized = RollNextSpawnTime(); + + _spawnInfos = relicSpawnInfoRepository.GetAll(); + } + + public override void Start() + { + base.Start(); + var outposts = _zone.Units.OfType().ToList(); + foreach (var outpost in outposts) + { + outpostRelicManagers.Add(new OutpostRelicManager(outpost)); + } + foreach (var childManagers in outpostRelicManagers) + { + childManagers.Start(); + } + } + + public override void Stop() + { + foreach (var childManagers in outpostRelicManagers) + { + childManagers.Stop(); + } + base.Stop(); + } + + public override void Update(TimeSpan time) + { + base.Update(time); + foreach (var childManagers in outpostRelicManagers) + { + childManagers.Update(time); + } + } + + protected override IRelic MakeRelic(RelicInfo info, Position position) + { + return Relic.BuildAndAddToZone(info, _zone, position, relicLootGenerator.GenerateLoot(info)); + } + + protected override TimeSpan RollNextSpawnTime() + { + var randomFactor = _random.NextDouble() - 0.5; + var minutesToAdd = RESPAWN_RANDOM_WINDOW.TotalMinutes * randomFactor; + + return _respawnRate.Add(TimeSpan.FromMinutes(minutesToAdd)); + } + + + protected override RelicInfo GetNextRelicType() + { + var spawnRates = _spawnInfos; + double sumRate = spawnRates.Sum(r => r.GetRate()); + double minRate = 0.0; + double chance = _random.NextDouble(); + RelicInfo info = null; + foreach (var spawnRate in spawnRates) + { + double rate = (double)spawnRate.GetRate() / sumRate; + double maxRate = rate + minRate; + + if (minRate < chance && chance <= maxRate) + { + info = spawnRate.GetRelicInfo(); + break; + } + minRate += rate; + } + return info; + } + + protected override Point FindRelicPosition(RelicInfo info) + { + if (info.HasStaticPosistion) //If the relic spawn info has a valid static position defined - use that + { + return info.GetPosition().ToPoint(); + } + return _spawnPosFinder.FindSpawnPosition(); //Else use random-walkable + } + + protected override List> DoGetRelicListDictionary() + { + var list = new List>(); + foreach (var childManagers in outpostRelicManagers) + { + list.AddMany(childManagers.GetRelicListDictionary()); + } + list.AddMany(_relics.Select(r => r.ToDebugDictionary()).ToList()); + return list; + } + + protected override void RefreshBeam(IRelic relic) + { + var info = relic.GetRelicInfo(); + var level = info.GetLevel(); + var faction = info.GetFaction(); + var position = relic.GetPosition(); + var factionalBeamType = BeamType.orange_20sec; + switch (faction) + { + case 0: + factionalBeamType = BeamType.orange_20sec; + break; + case 1: + factionalBeamType = BeamType.green_20sec; + break; + case 2: + factionalBeamType = BeamType.blue_20sec; + break; + case 3: + factionalBeamType = BeamType.red_20sec; + break; + default: + factionalBeamType = BeamType.orange_20sec; + break; + } + + var p = _zone.FixZ(position); + var beamBuilder = Beam.NewBuilder().WithType(BeamType.artifact_radar).WithTargetPosition(position) + .WithState(BeamState.AlignToTerrain) + .WithDuration(_relicRefreshRate); + _zone.CreateBeam(beamBuilder); + beamBuilder = Beam.NewBuilder().WithType(BeamType.nature_effect).WithTargetPosition(position) + .WithState(BeamState.AlignToTerrain) + .WithDuration(_relicRefreshRate); + _zone.CreateBeam(beamBuilder); + for (var i = 0; i < level; i++) + { + beamBuilder = Beam.NewBuilder().WithType(factionalBeamType).WithTargetPosition(p.AddToZ(3.5 * i + 1.0)) + .WithState(BeamState.Hit) + .WithDuration(_relicRefreshRate); + _zone.CreateBeam(beamBuilder); + } + } + } +} diff --git a/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs b/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs new file mode 100644 index 000000000..dacb4e5ce --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs @@ -0,0 +1,114 @@ +using Perpetuum.Data; +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Services.Relics +{ + + public class RelicSpawnInfoRepository + { + private RelicSpawnInfoReader _relicSpawnInfoReader; + + public RelicSpawnInfoRepository(IZone zone) + { + _relicSpawnInfoReader = new RelicSpawnInfoReader(zone); + } + + public IEnumerable GetAll() + { + return _relicSpawnInfoReader.GetAll(); + } + + } + + + public class RelicSpawnInfoReader + { + + private IZone _zone; + + public RelicSpawnInfoReader(IZone zone) + { + _zone = zone; + } + + protected RelicSpawnInfo CreateRelicSpawnInfoFromRecord(IDataRecord record) + { + var id = record.GetValue("id"); + var relicinfoid = record.GetValue("relictypeid"); + var zoneid = record.GetValue("zoneid"); + var rate = record.GetValue("rate"); + var x = record.GetValue("x"); + var y = record.GetValue("y"); + + var info = RelicInfo.GetByIDFromDB(relicinfoid); + + var config = new RelicSpawnInfo(info, _zone, rate, x, y); + + return config; + } + + public IEnumerable GetAll() + { + var relicZoneConfigs = Db.Query().CommandText("SELECT id, relictypeid, zoneid, rate, x, y FROM relicspawninfo WHERE zoneid = @zoneId") + .SetParameter("@zoneId", _zone.Id) + .Execute() + .Select(CreateRelicSpawnInfoFromRecord); + + return relicZoneConfigs.ToList(); + } + } + + + public class RelicSpawnInfo + { + private RelicInfo _info; + private IZone _zone; + private int _rate; + private int? _x; + private int? _y; + + + public RelicSpawnInfo(RelicInfo info, IZone zone, int rate, int? x, int? y) + { + _info = info; + _zone = zone; + _rate = rate; + _x = x; + _y = y; + if (HasPosition()) + { + this._info.SetPosition(GetPosition()); + } + } + + public int GetRate() + { + return _rate; + } + + public RelicInfo GetRelicInfo() + { + + return this._info; + } + + public bool HasPosition() + { + return _x != null && _y != null; + } + + public Position GetPosition() + { + double x = _x ?? 0; + double y = _y ?? 0; + return new Position(x, y); + } + + } +} diff --git a/src/Perpetuum/Services/Relics/RelicZoneConfig.cs b/src/Perpetuum/Services/Relics/RelicZoneConfig.cs new file mode 100644 index 000000000..ee1660fa6 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicZoneConfig.cs @@ -0,0 +1,88 @@ +using Perpetuum.Data; +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Services.Relics +{ + + public class RelicZoneConfigRepository + { + private RelicZoneConfigReader _relicZoneConfigReader; + private IZone _zone; + + public RelicZoneConfigRepository(IZone zone) + { + _relicZoneConfigReader = new RelicZoneConfigReader(zone); + _zone = zone; + } + + public RelicZoneConfig GetZoneConfig() + { + return _relicZoneConfigReader.GetZoneRelicConfiguration(); + } + + } + + + public class RelicZoneConfigReader + { + + private IZone _zone; + + public RelicZoneConfigReader(IZone zone) + { + _zone = zone; + } + + protected RelicZoneConfig CreateRelicZoneConfigFromRecord(IDataRecord record) + { + var id = record.GetValue("id"); + var zoneid = record.GetValue("zoneid"); + var maxspawn = record.GetValue("maxspawn"); + var respawnrate = record.GetValue("respawnrate"); + + var config = new RelicZoneConfig(zoneid, maxspawn, TimeSpan.FromSeconds(respawnrate)); + + return config; + } + + public RelicZoneConfig GetZoneRelicConfiguration() + { + var relicZoneConfigs = Db.Query().CommandText("SELECT TOP 1 id, zoneid, maxspawn, respawnrate FROM reliczoneconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", _zone.Id) + .Execute() + .Select(CreateRelicZoneConfigFromRecord); + + return relicZoneConfigs.SingleOrDefault(); + } + } + + public class RelicZoneConfig + { + private int _zoneid; + private int _max; + private TimeSpan _refreshRate; + + public RelicZoneConfig(int zoneid, int max, TimeSpan refreshRate) + { + _zoneid = zoneid; + _max = max; + _refreshRate = refreshRate; + } + + public TimeSpan GetTimeSpan() + { + return _refreshRate; + } + + public int GetMax() + { + return _max; + } + } +} diff --git a/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs b/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs new file mode 100644 index 000000000..942582449 --- /dev/null +++ b/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs @@ -0,0 +1,170 @@ +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Services.Looting; +using Perpetuum.Threading; +using Perpetuum.Units; +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Perpetuum.Services.Relics +{ + + public class AbstractRelic : Unit, IRelic + { + protected RelicInfo _info; + protected IZone _zone; + protected bool _alive; + + protected const double ACTIVATION_RANGE = 3; //30m + protected TimeSpan lifespan = TimeSpan.Zero; + protected virtual TimeSpan MAXLIFESPAN { get { return TimeSpan.FromDays(3); } } + + // Locks - Be sure to use locks independently and do not nest calls into other locks! + // Lock for Live/Dead state of Relic + private ReaderWriterLockSlim _lock; + // Lock for altering and reading the time left in this relic's lifepsan + private ReaderWriterLockSlim _lifespanLock; + private readonly TimeSpan THREAD_TIMEOUT = TimeSpan.FromSeconds(4); + + protected RelicLootItems _loots; + + [CanBeNull] + public static IRelic BuildAndAddToZone(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems) + { + var relic = (AbstractRelic)CreateUnitWithRandomEID(DefinitionNames.RELIC); + if (relic == null) + return null; + relic.Init(info, zone, position, lootItems); + relic.AddToZone(zone, position); + return relic; + } + + public void Init(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems) + { + _lock = new ReaderWriterLockSlim(); + _lifespanLock = new ReaderWriterLockSlim(); + _info = info; + _zone = zone; + CurrentPosition = _zone.GetPosition(position); + SetAlive(true); + SetLoots(lootItems); + } + + public void SetLoots(RelicLootItems lootItems) + { + _loots = lootItems; + } + + public RelicInfo GetRelicInfo() + { + return _info; + } + + public Position GetPosition() + { + return CurrentPosition; + } + + public void SetAlive(bool isAlive) + { + using (_lock.Write(THREAD_TIMEOUT)) + _alive = isAlive; + } + + public bool IsAlive() + { + using (_lock.Read(THREAD_TIMEOUT)) + return _alive; + } + + private void incrementLifeSpan(TimeSpan time) + { + using (_lifespanLock.Write(THREAD_TIMEOUT)) + lifespan += time; + } + + private bool isLifeSpanExpired() + { + using (_lifespanLock.Read(THREAD_TIMEOUT)) + return lifespan > MAXLIFESPAN; + } + + private TimeSpan GetLifeSpan() + { + using (_lifespanLock.Read(THREAD_TIMEOUT)) + return lifespan; + } + + protected override void OnUpdate(TimeSpan time) + { + incrementLifeSpan(time); + if (isLifeSpanExpired()) + SetAlive(false); + base.OnUpdate(time); + } + + protected override void OnRemovedFromZone(IZone zone) + { + SetAlive(false); + base.OnRemovedFromZone(zone); + } + + public void RemoveFromZone() + { + base.RemoveFromZone(); + } + + + protected internal override void UpdatePlayerVisibility(Player player) + { + if (GetPosition().TotalDistance2D(player.CurrentPosition) < ACTIVATION_RANGE && IsAlive()) + { + PopRelic(player); + } + } + + public virtual void PopRelic(Player player) + { + //Set flag on relic for removal + SetAlive(false); + + //Compute loots + if (_loots == null) + return; + + //Compute EP + var ep = GetRelicInfo().GetEP(); + if (_zone.Configuration.Type == ZoneType.Pvp) ep *= 2; + if (_zone.Configuration.Type == ZoneType.Training) ep = 0; + + //Fork task to make the lootcan and log the ep + Task.Run(() => + { + using (var scope = Db.CreateTransaction()) + { + LootContainer.Create().SetOwner(player).SetEnterBeamType(BeamType.loot_bolt).AddLoot(_loots.LootItems).BuildAndAddToZone(_zone, CurrentPosition); + if (ep > 0) player.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Artifact, ep); + scope.Complete(); + } + }); + } + + public Dictionary ToDebugDictionary() + { + var lifeLeft = MAXLIFESPAN - GetLifeSpan(); + var dictionary = new Dictionary + { + {k.position, this.GetPosition() }, + {k.timeLeft, lifeLeft }, + {"relicinfo", this.GetRelicInfo().ToDictionary().ToDebugString()}, + }; + + return dictionary; + } + } + +} diff --git a/src/Perpetuum/Services/Relics/Relics/IRelic.cs b/src/Perpetuum/Services/Relics/Relics/IRelic.cs new file mode 100644 index 000000000..fa516cc69 --- /dev/null +++ b/src/Perpetuum/Services/Relics/Relics/IRelic.cs @@ -0,0 +1,23 @@ +using Perpetuum.Players; +using Perpetuum.Zones; +using System.Collections.Generic; + +namespace Perpetuum.Services.Relics +{ + + public interface IRelic + { + void Init(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems); + void SetLoots(RelicLootItems lootItems); + RelicInfo GetRelicInfo(); + Position GetPosition(); + void SetAlive(bool isAlive); + bool IsAlive(); + void PopRelic(Player player); + Dictionary ToDebugDictionary(); + void RemoveFromZone(); + } + +} + + diff --git a/src/Perpetuum/Services/Relics/Relics/Relic.cs b/src/Perpetuum/Services/Relics/Relics/Relic.cs new file mode 100644 index 000000000..6eb69be14 --- /dev/null +++ b/src/Perpetuum/Services/Relics/Relics/Relic.cs @@ -0,0 +1,14 @@ +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Services.Looting; +using Perpetuum.Threading; +namespace Perpetuum.Services.Relics +{ + + public class Relic : AbstractRelic + { + + } + +} diff --git a/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs new file mode 100644 index 000000000..9e83a6d1b --- /dev/null +++ b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs @@ -0,0 +1,46 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.Intrusion; +using System; + +namespace Perpetuum.Services.Relics +{ + public class SAPRelic : AbstractRelic + { + private Outpost _outpost; + protected override TimeSpan MAXLIFESPAN { get { return TimeSpan.FromHours(12); } } + + [CanBeNull] + public static IRelic BuildAndAddToZone(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems, Outpost outpost) + { + var relic = (SAPRelic)CreateUnitWithRandomEID(DefinitionNames.RELIC_SAP); + if (relic == null) + return null; + relic.Init(info, zone, position, lootItems); + relic.SetOutpost(outpost); + relic.AddToZone(zone, position); + return relic; + } + + private void SetOutpost(Outpost outpost) + { + _outpost = outpost; + } + + public override void PopRelic(Player player) + { + var builder = StabilityAffectingEvent.Builder() + .WithOutpost(_outpost) + .WithSapDefinition(Definition) + .WithSapEntityID(Eid) + .WithPoints(1) + .AddParticipant(player) + .WithWinnerCorp(player.CorporationEid); + player.ApplyPvPEffect(); + _outpost.PublishSAPEvent(builder.Build()); + base.PopRelic(player); + } + } +} diff --git a/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs new file mode 100644 index 000000000..c87206e72 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs @@ -0,0 +1,194 @@ +using Perpetuum.Collections; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using Perpetuum.Zones; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Services.RiftSystem +{ + public interface ICustomRiftConfigReader + { + IEnumerable RiftConfigs { get; } + CustomRiftConfig GetById(int id); + } + + public class CustomRiftConfigReader : ICustomRiftConfigReader + { + public IEnumerable RiftConfigs + { + get { return _riftconfigs.Values; } + } + private readonly IDictionary _riftconfigs; + + public CustomRiftConfigReader() + { + _riftconfigs = Database.CreateCache("riftconfigs", "id", r => + { + var id = r.GetValue("id"); + var name = r.GetValue("name"); + var destinationGroupId = r.GetValue("destinationGroupId"); + var lifetimeSeconds = r.GetValue("lifespanSeconds") ?? 0; + var maxUses = r.GetValue("maxUses") ?? -1; + var catExcludeGroupId = r.GetValue("categoryExclusionGroupId") ?? -1; + + var destinations = GetDestinations(destinationGroupId); + var excludedCats = GetExclusionCategories(catExcludeGroupId); + return new CustomRiftConfig(id, name, destinations, maxUses, TimeSpan.FromSeconds(lifetimeSeconds), excludedCats); + }); + } + + private WeightedCollection GetDestinations(int destinationGroupId) + { + var group = Db.Query().CommandText( + @"SELECT id, groupId, zoneId, x, y, weight FROM riftdestinations WHERE groupId=@groupId + AND zoneId IN (SELECT id FROM zones WHERE enabled=1 AND zoneId=id);") + .SetParameter("@groupId", destinationGroupId) + .Execute() + .Select((record) => + { + var groupId = record.GetValue("groupId"); + var zoneId = record.GetValue("zoneId"); + var x = record.GetValue("x"); + var y = record.GetValue("y"); + var weight = record.GetValue("weight"); + + return new Destination(groupId, zoneId, x, y, weight); + }); + var collection = new WeightedCollection(); + foreach (var destination in group) + { + collection.Add(destination, destination.Weight); + } + return collection; + } + + private CategoryFlags[] GetExclusionCategories(int categoryGroupId) + { + var group = Db.Query().CommandText( + @"SELECT id, groupId, category FROM categorygroups WHERE groupId=@groupId;") + .SetParameter("@groupId", categoryGroupId) + .Execute() + .Select((record) => + { + var catValue = record.GetValue("category"); + return EnumHelper.GetEnum(catValue); + }); + return group.ToArray(); + } + + public CustomRiftConfig GetById(int id) + { + return RiftConfigs.FirstOrDefault(r => r.Id == id); + } + } + + public static class CustomRiftSpawner + { + public static bool TrySpawnRift(CustomRiftConfig config, IZoneManager zoneManager, int sourceZoneId, Position sourcePosition, Func riftFactory) + { + var zone = zoneManager.GetZone(sourceZoneId); + var targetDestination = config.GetDestination(); + if (targetDestination == null) + { + Logger.Error($"TargettedRift failed to spawn with config {config} \ntargetDestination == null"); + return false; + } + + var zoneTarget = zoneManager.GetZone(targetDestination.ZoneId); + if (zoneTarget == null) + { + Logger.Error($"TargettedRift failed to spawn with config {config} \nzoneTarget == null"); + return false; + } + + var targetPos = targetDestination.GetPosition(zoneTarget); + var rift = riftFactory(); + rift.SetTarget(zoneTarget, targetPos); + rift.SetConfig(config); + rift.AddToZone(zone, sourcePosition, ZoneEnterType.NpcSpawn); + + Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2})", zone.Id, rift.ED.Name, rift.CurrentPosition)); + return true; + } + } + + public class CustomRiftConfig + { + public int Id { get; private set; } + public string Name { get; private set; } + public TimeSpan Lifespan { get; private set; } + public int MaxUses { get; private set; } + public CategoryFlags[] ExcludeClasses { get; private set; } + private readonly WeightedCollection _destinations; + + public CustomRiftConfig(int id, string name, WeightedCollection destinations, int maxUses, TimeSpan lifespan, CategoryFlags[] excludeClasses) + { + Id = id; + Name = name; + _destinations = destinations; + MaxUses = maxUses; + Lifespan = lifespan; + ExcludeClasses = excludeClasses; + } + + public bool IsDespawning { get { return !Lifespan.Equals(TimeSpan.Zero); } } + public bool InfiniteUses { get { return MaxUses < 0; } } + + public bool IsExcluded(CategoryFlags category) + { + return category.IsAny(ExcludeClasses); + } + + [CanBeNull] + public Destination GetDestination() + { + return _destinations.GetRandom(); + } + + public override string ToString() + { + return $"CustomRiftConfig:id:{Id};n:{Name};l:{Lifespan};u:{MaxUses};"; + } + } + + + public class Destination + { + private Position Location { get; set; } + public bool IsRandomLocation { get { return Location.Equals(Position.Empty); } } + public int ZoneId { get; private set; } + public int Weight { get; private set; } + public int Group { get; private set; } + + public Destination(int groupId, int zoneId, int? x, int? y, int weight = 1) + { + Group = groupId; + ZoneId = zoneId; + Location = x == null || y == null ? Position.Empty : new Position(x ?? 0, y ?? 0); + Weight = weight; + } + + public Position GetPosition(IZone zone) + { + if (IsRandomLocation) + { + var randomFinder = new RandomPassablePositionFinder(zone); + if (randomFinder.Find(out Position random)) + { + return random; + } + } + var closestFinder = new ClosestWalkablePositionFinder(zone, Location); + if (closestFinder.Find(out Position closest)) + { + return closest; + } + return Location; + } + } +} diff --git a/src/Perpetuum/Services/RiftSystem/IRiftManager.cs b/src/Perpetuum/Services/RiftSystem/IRiftManager.cs new file mode 100644 index 000000000..c0542f55e --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/IRiftManager.cs @@ -0,0 +1,9 @@ +using System; + +namespace Perpetuum.Services.RiftSystem +{ + public interface IRiftManager + { + void Update(TimeSpan time); + } +} diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index ff9e047f5..df91e80bc 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -1,9 +1,5 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Perpetuum.Deployers; +using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Log; using Perpetuum.Players; @@ -13,10 +9,55 @@ using Perpetuum.Zones.Blobs.BlobEmitters; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.Teleporting.Strategies; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace Perpetuum.Services.RiftSystem { - public abstract class Portal : Unit,IUsableItem + public interface IConfigurablePortal + { + void SetConfig(CustomRiftConfig riftConfig); + void SetTarget(IZone zone, Position position); + } + + public interface IDespawningPortal + { + void SetDespawnTime(TimeSpan despawnTime); + } + + /// + /// Super class to all Rifts + /// + public abstract class Portal : Unit, IUsableItem + { + public virtual void UseItem(Player player) + { + player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); + player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); + player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + } + + /// + /// Rifts that despawn + /// + public abstract class DespawningPortal : Portal, IDespawningPortal { private UnitDespawnHelper _despawnHelper; @@ -30,16 +71,96 @@ protected override void OnUpdate(TimeSpan time) _despawnHelper?.Update(time, this); base.OnUpdate(time); } + } - public virtual void UseItem(Player player) + /// + /// Rifts that can have a configurable destination when used + /// + public class DespawningTargettedPortal : DespawningPortal, IConfigurablePortal + { + protected readonly ITeleportStrategyFactories _teleportStrategyFactories; + public IZone TargetZone { get; private set; } + public Position TargetPosition { get; private set; } + public CustomRiftConfig RiftConfig { get; private set; } + private int uses = 0; + + public DespawningTargettedPortal(ITeleportStrategyFactories teleportStrategyFactories) { - player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); - player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); + _teleportStrategyFactories = teleportStrategyFactories; + } + + public void SetConfig(CustomRiftConfig riftConfig) + { + RiftConfig = riftConfig; + // Only set despawner if config is despawning + if (RiftConfig.IsDespawning) + { + SetDespawnTime(RiftConfig.Lifespan); + } + } + + public void SetTarget(IZone zone, Position position) + { + TargetZone = zone; + TargetPosition = position; + } + + private bool HasTarget => TargetZone != null && !TargetPosition.Equals(Position.Empty); + + private bool IsUsageExceeded() + { + return !RiftConfig.InfiniteUses && RiftConfig.MaxUses < uses; + } + + private void IncrementUsage() + { + if (!RiftConfig.InfiniteUses) + { + uses++; + } + } + + private bool IsExcluded(Player player) + { + return RiftConfig.IsExcluded(player.ED.CategoryFlags); + } + + public override void UseItem(Player player) + { + HasTarget.ThrowIfFalse(ErrorCodes.NoTeleportDestinations); + base.UseItem(player); + + IsUsageExceeded().ThrowIfTrue(ErrorCodes.MaximumAllowedRegistrationExceeded); + + IsExcluded(player).ThrowIfTrue(ErrorCodes.RobotWrongType); + + TeleportToAnotherZone teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(TargetZone); + teleport.TargetPosition = TargetPosition; + _ = teleport.DoTeleportAsync(player); + + IncrementUsage(); + + if (IsUsageExceeded()) + { + Kill(); + } } } - public class RandomRiftPortal : Portal + public class StrongholdExitRift : DespawningTargettedPortal + { + public StrongholdExitRift(ITeleportStrategyFactories teleportStrategyFactories) : base(teleportStrategyFactories) { } + } + + public class StrongholdEntryRift : DespawningTargettedPortal + { + public StrongholdEntryRift(ITeleportStrategyFactories teleportStrategyFactories) : base(teleportStrategyFactories) { } + } + + /// + /// RandomRift (used as the Rift Echo after a TAP) + /// + public class RandomRiftPortal : DespawningPortal { private readonly ITeleportStrategyFactories _teleportStrategyFactories; private Position _targetPosition; @@ -51,7 +172,7 @@ public RandomRiftPortal(ITeleportStrategyFactories teleportStrategyFactories) protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { - var rift = Zone.Units.OfType().RandomElement(); + Rift rift = Zone.Units.OfType().RandomElement(); if (rift != null) { _targetPosition = rift.CurrentPosition; @@ -64,14 +185,14 @@ public override void UseItem(Player player) { base.UseItem(player); - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); + TeleportWithinZone teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); teleport.TargetPosition = _targetPosition; teleport.ApplyTeleportSickness = true; teleport.ApplyInvulnerable = true; - teleport.DoTeleportAsync(player); + _ = teleport.DoTeleportAsync(player); } } - + public class RiftNpcGroupInfo { public int presenceID; @@ -80,65 +201,73 @@ public class RiftNpcGroupInfo public Player ownerPlayer; } + /// + /// TAP deployment + /// public class RiftActivator : ItemDeployerBase { public override void Deploy(IZone zone, Player player) { - var rift = player.GetUnitsWithinRange(5).FirstOrDefault(); + Rift rift = player.GetUnitsWithinRange(5).FirstOrDefault(); if (rift == null) + { throw new PerpetuumException(ErrorCodes.RiftOutOfRange); + } - var maxRiftLevel = 1; - - if (zone.Configuration.IsAlpha) - maxRiftLevel = 1; - else if (zone.Configuration.IsBeta) - maxRiftLevel = 2; - else if (zone.Configuration.IsGamma) - maxRiftLevel = 3; - - if (ED.Tier.level > maxRiftLevel) + if (ED.Tier.level > rift.MaxTAPLevel) + { throw new PerpetuumException(ErrorCodes.RiftLevelMismatch); + } - var info = new RiftNpcGroupInfo(); + if (zone is StrongHoldZone) + { + throw new PerpetuumException(ErrorCodes.ItemNotUsable); + } + + RiftNpcGroupInfo info = new RiftNpcGroupInfo(); Debug.Assert(DeployableItemEntityDefault.Config.npcPresenceId != null, "DeployableItemEntityDefault.Config.npcPresenceId != null"); info.presenceID = (int)DeployableItemEntityDefault.Config.npcPresenceId; Debug.Assert(DeployableItemEntityDefault.Config.lifeTime != null, "DeployableItemEntityDefault.Config.lifeTime != null"); info.presenceLifeTime = TimeSpan.FromMilliseconds((double)DeployableItemEntityDefault.Config.lifeTime); Debug.Assert(DeployableItemEntityDefault.Config.waves != null, "DeployableItemEntityDefault.Config.waves != null"); - info.wavesCount = (int) DeployableItemEntityDefault.Config.waves; + info.wavesCount = (int)DeployableItemEntityDefault.Config.waves; info.ownerPlayer = player; if (!rift.TryActivate(info)) + { throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } - LogTransaction(player,this); + LogTransaction(player, this); - var seei = new SummonEggEventInfo(player,DeployableItemEntityDefault.Definition, player.CurrentPosition); + SummonEggEventInfo seei = new SummonEggEventInfo(player, DeployableItemEntityDefault.Definition, player.CurrentPosition); player.MissionHandler.EnqueueMissionEventInfo(seei); } } - public class Rift : Unit,IUsableItem,IBlobEmitter + /// + /// Regular Rift, despawns, can activate a TAP on it, can jump, emits interference + /// + public class Rift : DespawningPortal, IBlobEmitter { private readonly ITeleportStrategyFactories _teleportStrategyFactories; - private UnitDespawnHelper _despawnHelper; private readonly BlobEmitter _blobEmitter; + public int MaxTAPLevel { get; private set; } + public Rift(ITeleportStrategyFactories teleportStrategyFactories) { _teleportStrategyFactories = teleportStrategyFactories; _blobEmitter = new BlobEmitter(this); } - public void SetDespawnTime(TimeSpan despawnTime) + public void SetLevel(int maxLevel) { - _despawnHelper = UnitDespawnHelper.Create(this,despawnTime); + MaxTAPLevel = maxLevel; } protected override void OnUpdate(TimeSpan time) { - _despawnHelper?.Update(time, this); base.OnUpdate(time); } @@ -146,18 +275,27 @@ protected override void OnUpdate(TimeSpan time) public bool TryActivate(RiftNpcGroupInfo info) { - var zone = Zone; + IZone zone = Zone; if (zone == null) + { + return false; + } + + if (zone is StrongHoldZone) + { return false; - + } + if (Interlocked.CompareExchange(ref _activated, 1, 0) == 1) + { return false; + } zone.CreateBeam(BeamType.npc_egg_beam, builder => builder.WithPosition(CurrentPosition).WithDuration(6000)); - Task.Delay(6000).ContinueWith(t => + _ = Task.Delay(6000).ContinueWith(t => { - var presence = (DynamicPoolPresence)zone.PresenceManager.CreatePresence(info.presenceID); + DynamicPoolPresence presence = (DynamicPoolPresence)zone.PresenceManager.CreatePresence(info.presenceID); presence.DynamicPosition = CurrentPosition; presence.LifeTime = info.presenceLifeTime; presence.Summoner = info.ownerPlayer.Character; @@ -171,25 +309,26 @@ public bool TryActivate(RiftNpcGroupInfo info) return true; } - public void UseItem(Player player) + public override void UseItem(Player player) { - player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); - player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); + base.UseItem(player); - var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); + Rift nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); if (nearestRift == null) + { throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); + TeleportWithinZone teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); teleport.TargetPosition = nearestRift.CurrentPosition; teleport.ApplyTeleportSickness = true; teleport.ApplyInvulnerable = true; - teleport.DoTeleportAsync(player); + _ = teleport.DoTeleportAsync(player); + } public double BlobEmission => _blobEmitter.BlobEmission; public double BlobEmissionRadius => _blobEmitter.BlobEmissionRadius; } -} \ No newline at end of file +} diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 4662b4aac..fa2411e8a 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -55,33 +55,37 @@ protected override Point FindSpawnPosition(IZone zone) } } - public class RiftManager + public class RiftManager : IRiftManager { + private readonly ZoneRiftConfig _config; private readonly IZone _zone; private readonly TimeRange _spawnTime; private readonly RiftSpawnPositionFinder _spawnPositionFinder; private readonly IEntityServices _entityServices; + private readonly ZoneRiftConfigRepository zoneRiftConfigRepository; private readonly LinkedList _nextRiftSpawns = new LinkedList(); - public RiftManager(IZone zone,TimeRange spawnTime,RiftSpawnPositionFinder spawnPositionFinder,IEntityServices entityServices) + public RiftManager(IZone zone, TimeRange spawnTime, RiftSpawnPositionFinder spawnPositionFinder, IEntityServices entityServices) { _zone = zone; _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; + zoneRiftConfigRepository = new ZoneRiftConfigRepository(zone); + _config = zoneRiftConfigRepository.GetZoneConfig(); } private int _riftCounts; public void Update(TimeSpan time) { - while (_riftCounts < 20) + while (_riftCounts < _config.GetMaxRifts()) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); } - + _nextRiftSpawns.RemoveAll(t => { t.Update(time); @@ -98,11 +102,13 @@ private void SpawnRift() var rift = (Rift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.RIFT); rift.SetDespawnTime(TimeSpan.FromHours(3)); + rift.SetLevel(_config.GetMaxLevel()); rift.RemovedFromZone += OnRiftRemovedFromZone; var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); + rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); - Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ")"); + Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2})", _zone.Id, rift.ED.Name, rift.CurrentPosition)); } private void OnRiftRemovedFromZone(Unit unit) @@ -110,4 +116,4 @@ private void OnRiftRemovedFromZone(Unit unit) Interlocked.Decrement(ref _riftCounts); } } -} \ No newline at end of file +} diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs new file mode 100644 index 000000000..6eafd17d0 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs @@ -0,0 +1,52 @@ +using Perpetuum.Data; +using Perpetuum.Zones; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Services.RiftSystem.StrongholdRifts +{ + /// + /// DB query for StrongholdRiftLocation + /// + public static class StrongholdRiftLocationRepository + { + /// + /// Get all StrongholdRiftLocations for the provided zone + /// + /// IZone + /// IEnumerable of StrongholdRiftLocation + public static IEnumerable GetAllInZone(IZone zone, ICustomRiftConfigReader customRiftConfigReader) + { + var locations = Db.Query().CommandText("SELECT id, zoneId, x, y, riftConfigId FROM strongholdexitconfig WHERE zoneId = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute() + .Select((record) => + { + var x = record.GetValue("x"); + var y = record.GetValue("y"); + var riftConfigId = record.GetValue("riftConfigId"); + var riftConfig = customRiftConfigReader.GetById(riftConfigId); + return new StrongholdRiftLocation(zone, new Position(x, y), riftConfig); + }); + + return locations; + } + } + + /// + /// Spawn of StrongholdExitRift + /// + public class StrongholdRiftLocation + { + public IZone Zone { get; private set; } + public Position Location { get; private set; } + public CustomRiftConfig RiftConfig { get; private set; } + + public StrongholdRiftLocation(IZone zone, Position location, CustomRiftConfig riftConfig) + { + Zone = zone; + Location = location; + RiftConfig = riftConfig; + } + } +} diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs new file mode 100644 index 000000000..25439c693 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs @@ -0,0 +1,56 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Zones; +using System; +using System.Collections.Generic; + +namespace Perpetuum.Services.RiftSystem.StrongholdRifts +{ + /// + /// RiftManager for Stronghold + /// + public class StrongholdRiftManager : IRiftManager + { + private readonly IZone _zone; + private readonly IZoneManager _zoneManager; + private readonly IEntityServices _entityServices; + private readonly IEnumerable _spawnLocations; + + public StrongholdRiftManager(IZone zone, IEntityServices entityServices, ICustomRiftConfigReader customRiftConfigReader, IZoneManager zoneManager) + { + _zone = zone; + _spawnLocations = StrongholdRiftLocationRepository.GetAllInZone(zone, customRiftConfigReader); + _entityServices = entityServices; + _zoneManager = zoneManager; + } + + private bool _spawnedAll = false; + private bool _spawning = false; + public void Update(TimeSpan time) + { + if (_spawnedAll || _spawning) + return; + + SpawnAll(); + } + + private void SpawnAll() + { + _spawning = true; + foreach (var location in _spawnLocations) + { + SpawnRift(location); + } + _spawnedAll = true; + _spawning = false; + } + + private void SpawnRift(StrongholdRiftLocation spawn) + { + CustomRiftSpawner.TrySpawnRift(spawn.RiftConfig, _zoneManager, _zone.Id, spawn.Location, () => + { + return (StrongholdExitRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.STRONGHOLD_EXIT_RIFT); + }); + } + } +} diff --git a/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs b/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs new file mode 100644 index 000000000..c00a33c22 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs @@ -0,0 +1,89 @@ +using Perpetuum.Data; +using Perpetuum.Zones; +using System.Data; +using System.Linq; + +namespace Perpetuum.Services.RiftSystem +{ + + public class ZoneRiftConfigRepository + { + private ZoneRiftConfigReader _zoneRiftConfigReader; + private IZone _zone; + + public ZoneRiftConfigRepository(IZone zone) + { + _zoneRiftConfigReader = new ZoneRiftConfigReader(zone); + _zone = zone; + } + + public ZoneRiftConfig GetZoneConfig() + { + return _zoneRiftConfigReader.GetForZone(); + } + + } + + /// + /// DB query for ZoneRiftConfigs + /// + public class ZoneRiftConfigReader + { + private IZone _zone; + + public ZoneRiftConfigReader(IZone zone) + { + _zone = zone; + } + protected ZoneRiftConfig CreateZoneRiftConfigFromRecord(IDataRecord record) + { + var id = record.GetValue("id"); + var zoneid = record.GetValue("zoneid"); + var maxrifts = record.GetValue("maxrifts"); + var maxLevel = record.GetValue("maxlevel"); + + var config = new ZoneRiftConfig(zoneid, maxrifts, maxLevel); + + return config; + } + /// + /// Get the ZoneRiftConfig for a Zone + /// + /// IZone + /// ZoneRiftConfig + public ZoneRiftConfig GetForZone() + { + var riftConfigs = Db.Query().CommandText("SELECT TOP 1 id, zoneid, maxrifts, maxlevel FROM zoneriftsconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", _zone.Id) + .Execute() + .Select(CreateZoneRiftConfigFromRecord); + + return riftConfigs.FirstOrDefault(); + } + } + + /// + /// Zone specs for number of rifts and TAP level + /// + public class ZoneRiftConfig + { + private int _zoneid { get; set; } + private int _maxRifts { get; set; } + private int _maxLevel { get; set; } + public ZoneRiftConfig(int zoneid, int maxRifts, int maxLevel) + { + _zoneid = zoneid; + _maxRifts = maxRifts; + _maxLevel = maxLevel; + } + + public int GetMaxRifts() { + return _maxRifts; + } + + public int GetMaxLevel() { + return _maxLevel; + } + } +} + diff --git a/src/Perpetuum/Services/Sessions/ISessionManager.cs b/src/Perpetuum/Services/Sessions/ISessionManager.cs index e80237497..7767cec46 100644 --- a/src/Perpetuum/Services/Sessions/ISessionManager.cs +++ b/src/Perpetuum/Services/Sessions/ISessionManager.cs @@ -10,6 +10,7 @@ public interface ISessionManager ISession GetByCharacter(Character character); ISession GetByAccount(Account account); ISession GetByAccount(int accountId); + ISession GetByCharacter(int characterid); IEnumerable Sessions { get;} IEnumerable SelectedCharacters { get; } bool Contains(SessionID sessionId); diff --git a/src/Perpetuum/Services/Sessions/Session.cs b/src/Perpetuum/Services/Sessions/Session.cs index 50cd28f5a..3c5f4eaf9 100644 --- a/src/Perpetuum/Services/Sessions/Session.cs +++ b/src/Perpetuum/Services/Sessions/Session.cs @@ -27,13 +27,20 @@ public interface ISession bool IsAuthenticated { get; } Character Character { get; } AccessLevel AccessLevel { get; } + IZoneManager ZoneMgr { get; } + bool AccountCreatedInSession { get; set; } + string ClientVersion { get; set; } + int SteamBuild { get; set; } void SendMessage(MessageBuilder builder); void SendMessage(IMessage message); + // to expose these to our chat command interface. + IRequest CreateLocalRequest(string data); + void HandleLocalRequest(IRequest request); void Start(); - void SignIn(int accountID, string hwHash); + void SignIn(int accountID, string hwHash, int language); void SignOut(); void SelectCharacter(Character character); @@ -52,6 +59,7 @@ public class Session : ISession private readonly GlobalConfiguration _globalConfiguration; private readonly IAccountManager _accountManager; private readonly IZoneManager _zoneManager; + private readonly ICustomDictionary _customDictionary; private readonly Func _commandFactory; private readonly RequestHandlerFactory _requestHandlerFactory; private readonly RequestHandlerFactory _zoneRequestHandlerFactory; @@ -67,6 +75,7 @@ public class Session : ISession public Session(GlobalConfiguration globalConfiguration, IAccountManager accountManager, IZoneManager zoneManager, + ICustomDictionary customDictionary, Socket socket, Func commandFactory, RequestHandlerFactory requestHandlerFactory, @@ -80,6 +89,7 @@ public Session(GlobalConfiguration globalConfiguration, _globalConfiguration = globalConfiguration; _accountManager = accountManager; _zoneManager = zoneManager; + _customDictionary = customDictionary; _commandFactory = commandFactory; _requestHandlerFactory = requestHandlerFactory; _zoneRequestHandlerFactory = zoneRequestHandlerFactory; @@ -88,6 +98,8 @@ public Session(GlobalConfiguration globalConfiguration, } public AccessLevel AccessLevel => _accessLevel; + public string ClientVersion { get; set; } = string.Empty; + public int SteamBuild { get; set; } = 0; private bool SafeLogOut { get; set; } @@ -101,6 +113,14 @@ public void Start() _connection.Receive(); } + public IZoneManager ZoneMgr + { + get + { + return _zoneManager; + } + } + public SessionID Id { get; private set; } public int AccountId { get; private set; } @@ -120,6 +140,11 @@ public void Start() public event SessionEventHandler Disconnected; public event SessionEventHandler RsaKeyReceived; + + /// + /// mitigate account creation spam. now must disconnect before trying to make another account. + /// + public bool AccountCreatedInSession { get; set; } public void SendMessage(MessageBuilder messageBuilder) { @@ -172,7 +197,7 @@ public void ForceQuit(ErrorCodes error = ErrorCodes.NoError, string comment = nu _connection.Disconnect(); } - public void SignIn(int accountID, string hwHash) + public void SignIn(int accountID, string hwHash, int language) { if (IsAuthenticated) return; @@ -208,7 +233,14 @@ public void SignIn(int accountID, string hwHash) AccountId = account.Id; - var builder = Message.Builder.SetCommand(Commands.SignIn).WithData(account.ToDictionary()); + var response = account.ToDictionary(); + var customDictionary = _customDictionary.GetDictionary(language); + if (customDictionary != null && customDictionary.Count > 0) + { + response.Add(k.customDictionary, customDictionary); + } + + var builder = Message.Builder.SetCommand(Commands.SignIn).WithData(response); SendMessage(builder); }); @@ -312,6 +344,11 @@ private void OnDataReceived(ITcpConnection connection, byte[] receivedData) } } + public void HandleLocalRequest(IRequest request) + { + HandleRequest(request); + } + private void HandleRequest(IRequest request) { if (request is IZoneRequest zoneRequest) @@ -328,6 +365,11 @@ private void HandleRequest(IRequest request) requestHandler.HandleRequest(request); } + public IRequest CreateLocalRequest(string data) + { + return CreateRequest(data); + } + private IRequest CreateRequest(string data) { var args = data.Split(':'); diff --git a/src/Perpetuum/Services/Sessions/SessionManager.cs b/src/Perpetuum/Services/Sessions/SessionManager.cs index 17569a73d..fcc0a75c5 100644 --- a/src/Perpetuum/Services/Sessions/SessionManager.cs +++ b/src/Perpetuum/Services/Sessions/SessionManager.cs @@ -22,6 +22,7 @@ public class SessionManager : ISessionManager private readonly Session.Factory _sessionFactory; private readonly IRelayStateService _relayStateService; private readonly ISteamManager _steamManager; + private readonly GlobalConfiguration _globalConfiguration; private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); private readonly ConcurrentDictionary _charactersIndex = new ConcurrentDictionary(); @@ -30,10 +31,11 @@ public SessionManager(GlobalConfiguration globalConfiguration,IHostStateService var relayEndPoint = new IPEndPoint(IPAddress.Any,globalConfiguration.ListenerPort); _listener = new TcpListener(relayEndPoint); + _globalConfiguration = globalConfiguration; _sessionFactory = sessionFactory; _relayStateService = relayStateService; _steamManager = steamManager; - MaxSessions = 1000; + MaxSessions = 5000; hostStateService.StateChanged += (sender,state) => { @@ -79,9 +81,15 @@ private void OnSessionRsaKeyReceived(ISession session) #endif {"version",GenxyVersion.REVISION}, {k.OSTime,DateTime.Now}, - {"steamLoginEnabled",_steamManager.SteamAppID > 0} + {"steamLoginEnabled",_steamManager.SteamAppID > 0}, + {"username_is_email", 1 } }; + if (!string.IsNullOrEmpty(_globalConfiguration.ResourceServerURL)) + { + welcomeData.Add("resourceServerURL", _globalConfiguration.ResourceServerURL); + } + var builder = Message.Builder.SetCommand(Commands.Welcome).WithData(welcomeData); session.SendMessage(builder); } @@ -169,6 +177,17 @@ public ISession GetByAccount(int accountId) return _sessions.Values.FirstOrDefault(s => s.AccountId == accountId); } + [CanBeNull] + public ISession GetByCharacter(int characterid) + { + if (characterid == 0) + { + return null; + } + return _sessions.Values.FirstOrDefault(s => s.Character.Id == characterid); + } + + public bool IsOnline(Character character) { return _charactersIndex.ContainsKey(character); diff --git a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs index 026b1982b..fd46cbd48 100644 --- a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs +++ b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs @@ -6,7 +6,10 @@ namespace Perpetuum.Services.Sparks.Teleports { public interface ISparkTeleportRepository : IRepository { + SparkTeleport GetCommon(int id); + IEnumerable GetAllByCharacter(Character character); + IEnumerable GetAllByDockingBase(DockingBase dockingBase); } -} \ No newline at end of file +} diff --git a/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportHelper.cs b/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportHelper.cs index 1bca53759..01297c9b3 100644 --- a/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportHelper.cs +++ b/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportHelper.cs @@ -23,6 +23,11 @@ public SparkTeleport Get(int sparkTeleportID) return _sparkTeleportRepository.Get(sparkTeleportID); } + public SparkTeleport GetCommon(int sparkTeleportID) + { + return _sparkTeleportRepository.GetCommon(sparkTeleportID); + } + public SparkTeleport CreateSparkTeleport(DockingBase dockingBase, Character character) { var teleport = new SparkTeleport diff --git a/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportRepository.cs b/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportRepository.cs index 2b1277f06..1c21b8594 100644 --- a/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportRepository.cs +++ b/src/Perpetuum/Services/Sparks/Teleports/SparkTeleportRepository.cs @@ -29,6 +29,18 @@ public SparkTeleport Get(int id) return CreateFromRecord(record); } + public SparkTeleport GetCommon(int id) + { + var record = Db.Query().CommandText("select * from charactersparkteleports where baseeid=@ID and characterid=0") + .SetParameter("@ID", id) + .ExecuteSingleRow(); + + if (record == null) + return null; + + return CreateFromRecord(record); + } + public IEnumerable GetAll() { throw new NotImplementedException(); diff --git a/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs new file mode 100644 index 000000000..179bb3f46 --- /dev/null +++ b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs @@ -0,0 +1,122 @@ +using Perpetuum.Data; +using Perpetuum.Players; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using System; + +namespace Perpetuum.Services.Strongholds +{ + public interface IStrongholdPlayerStateManager + { + void OnPlayerEnterZone(Player player); + void OnPlayerExitZone(Player player); + } + + public class StrongholdPlayerStateManager : IStrongholdPlayerStateManager + { + /// + /// Handle player-stronghold state. + /// If the player is new to a stronghold zone, set the expiration + /// If the player is returning to the same stronghold zone (from logoff) apply existing expiration + /// + /// Zone the player is added to + /// The Player being added to zone + public static void OnPlayerAddToZone(Zone zone, Player player) + { + UpdatePlayerState(player, (p) => + { + if (!IsSameZone(p, zone)) + { + p.DynamicProperties.Remove(k.strongholdDespawnTime); + } + zone.PlayerStateManager?.OnPlayerEnterZone(p); + p.DynamicProperties.Update(k.prevZone, zone.Id); + }); + } + + private readonly IZone _zone; + private readonly TimeSpan MAX = TimeSpan.FromMinutes(60); + private readonly TimeSpan MIN = TimeSpan.FromSeconds(30); + + private readonly EventListenerService _eventChannel; + + public StrongholdPlayerStateManager(IZone zone, EventListenerService eventChannel) + { + _eventChannel = eventChannel; + _zone = zone; + MAX = TimeSpan.FromMinutes(_zone.Configuration.TimeLimitMinutes ?? 60); + } + + public static bool IsSameZone(Player player, IZone zone) + { + return player.DynamicProperties.GetOrDefault(k.prevZone) == zone.Id; + } + + public void OnPlayerEnterZone(Player player) + { + var now = DateTime.UtcNow; + if (!IsSameZone(player, _zone)) + { + player.DynamicProperties.Remove(k.strongholdDespawnTime); + } + var effectEnd = player.DynamicProperties.GetOrAdd(k.strongholdDespawnTime, now.Add(MAX)); + var effectDuration = (effectEnd - now).Max(MIN); + ApplyDespawn(player, effectDuration, effectEnd); + SendEntryMessage(player, effectDuration); + } + + public void OnPlayerExitZone(Player player) + { + player.ClearStrongholdDespawn(); + } + + private void ApplyDespawn(Player player, TimeSpan remaining, DateTime endTime) + { + player.DynamicProperties.Update(k.strongholdDespawnTime, endTime); + player.SetStrongholdDespawn(remaining, (u) => + { + if (u is Player p) + { + UpdatePlayerState(p, DoDespawnAction); + } + }); + } + + private void DoDespawnAction(Player player) + { + var dockingBase = player.Character.GetHomeBaseOrCurrentBase(); + player.DockToBase(dockingBase.Zone, dockingBase); + player.DynamicProperties.Remove(k.prevZone); + player.DynamicProperties.Remove(k.strongholdDespawnTime); + SendRemovalMessage(player); + } + + private static void UpdatePlayerState(Player player, Action transaction) + { + using (var scope = Db.CreateTransaction()) + { + transaction(player); + player.Save(); + scope.Complete(); + } + } + + private void SendEntryMessage(Player player, TimeSpan effectDuration) + { + var message = $"You only have {effectDuration.ToHumanTimeString()} remaining on this island before the Syndicate will teleport you home.\nGood luck!"; + SendMessage(player, message); + } + + private void SendRemovalMessage(Player player) + { + var message = $"You are back safely!\nThe Syndicate had to pull you out or risk losing your connection to those pesky Nians."; + SendMessage(player, message); + } + + private void SendMessage(Player player, string message) + { + _eventChannel.PublishMessage(new DirectMessage(player.Character, message)); + } + } +} diff --git a/src/Perpetuum/Services/TechTree/Kernel.cs b/src/Perpetuum/Services/TechTree/Kernel.cs index 43616b572..1870bc569 100644 Binary files a/src/Perpetuum/Services/TechTree/Kernel.cs and b/src/Perpetuum/Services/TechTree/Kernel.cs differ diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs index 7ee370cbe..b1acd4c83 100644 Binary files a/src/Perpetuum/Services/TechTree/Points.cs and b/src/Perpetuum/Services/TechTree/Points.cs differ diff --git a/src/Perpetuum/Services/TechTree/TechTreeNode.cs b/src/Perpetuum/Services/TechTree/TechTreeNode.cs index 2c4fbd554..9b96151ca 100644 Binary files a/src/Perpetuum/Services/TechTree/TechTreeNode.cs and b/src/Perpetuum/Services/TechTree/TechTreeNode.cs differ diff --git a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs index 5db5dc04f..5c2a928b6 100644 Binary files a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs and b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs differ diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs index ac0b90fbf..c7ee4e801 100644 Binary files a/src/Perpetuum/Services/TechTree/TechTreeService.cs and b/src/Perpetuum/Services/TechTree/TechTreeService.cs differ diff --git a/src/Perpetuum/Services/Weather/IWeatherService.cs b/src/Perpetuum/Services/Weather/IWeatherService.cs new file mode 100644 index 000000000..52b6ec95c --- /dev/null +++ b/src/Perpetuum/Services/Weather/IWeatherService.cs @@ -0,0 +1,15 @@ +using System; +using Perpetuum.Threading.Process; + +namespace Perpetuum.Services.Weather +{ + /// + /// Service that generates weather state-transitions + /// + public interface IWeatherService : IObservable, IProcess + { + [NotNull] + WeatherInfo GetCurrentWeather(); + void SetCurrentWeather(WeatherInfo weather); + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/Weather/WeatherInfo.cs b/src/Perpetuum/Services/Weather/WeatherInfo.cs new file mode 100644 index 000000000..a947bb4b1 --- /dev/null +++ b/src/Perpetuum/Services/Weather/WeatherInfo.cs @@ -0,0 +1,76 @@ +using System; +using System.Text; +using Perpetuum.Timers; +using Perpetuum.Zones; + +namespace Perpetuum.Services.Weather +{ + public class WeatherInfo + { + private const int BAD_WEATHER = 200; + private const int GOOD_WEATHER = 100; + + public const int WORST_WEATHER = 254; + public const int BEST_WEATHER = 0; + + public readonly int Current; + public readonly int Next; + + private readonly TimeTracker _timer; + + public WeatherInfo(int current, int next, TimeSpan duration) + { + Current = current; + Next = next; + _timer = new TimeTracker(duration); + } + + public bool Update(TimeSpan elapsed) + { + _timer.Update(elapsed); + return _timer.Expired; + } + + public Packet CreateUpdatePacket() + { + var packet = new Packet(ZoneCommand.Weather); + packet.AppendByte(0); // ez lesz majd a + packet.AppendByte((byte)Current); + packet.AppendByte((byte)Next); + packet.AppendLong((long)_timer.Elapsed.TotalMilliseconds); + packet.AppendLong((long)_timer.Duration.TotalMilliseconds); + return packet; + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("Command:{0}, current:{1}, next:{2}, elapsed:{3}, duration:{4}", + ZoneCommand.Weather, Current, Next, _timer.Elapsed.TotalSeconds, _timer.Duration.TotalSeconds); + return sb.ToString(); + } + + public enum WeatherState + { + GOOD_WEATHER, + NEUTRAL_WEATHER, + BAD_WEATHER + } + + public WeatherState getWeatherState() + { + if(Current > BAD_WEATHER && Next > BAD_WEATHER) + { + return WeatherState.BAD_WEATHER; + } + else if (Current < GOOD_WEATHER && Next < GOOD_WEATHER) + { + return WeatherState.GOOD_WEATHER; + } + else + { + return WeatherState.NEUTRAL_WEATHER; + } + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/Weather/WeatherObservers.cs b/src/Perpetuum/Services/Weather/WeatherObservers.cs new file mode 100644 index 000000000..c854f8568 --- /dev/null +++ b/src/Perpetuum/Services/Weather/WeatherObservers.cs @@ -0,0 +1,44 @@ +using Perpetuum.Reactive; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using System; + +namespace Perpetuum.Services.Weather +{ + /// + /// Weather observer for transmitting EventMessages to EventListenerService + /// + public class WeatherEventListener : Observer + { + private readonly IZone _zone; + private readonly EventListenerService _listener; + public WeatherEventListener(EventListenerService listener, IZone zone) + { + _zone = zone; + _listener = listener; + } + + public override void OnNext(WeatherInfo info) + { + _listener.PublishMessage(new WeatherEventMessage(info, _zone.Id)); + } + } + + /// + /// Generic weather observer that invokes the provided action on weather events + /// + public class WeatherMonitor : Observer + { + private readonly Action _onNext; + public WeatherMonitor(Action onNext) + { + _onNext = onNext; + } + + public override void OnNext(WeatherInfo info) + { + _onNext(info); + } + } +} diff --git a/src/Perpetuum/Zones/WeatherService.cs b/src/Perpetuum/Services/Weather/WeatherService.cs similarity index 57% rename from src/Perpetuum/Zones/WeatherService.cs rename to src/Perpetuum/Services/Weather/WeatherService.cs index 3b67df44d..3c353f543 100644 --- a/src/Perpetuum/Zones/WeatherService.cs +++ b/src/Perpetuum/Services/Weather/WeatherService.cs @@ -2,23 +2,24 @@ using Perpetuum.Reactive; using Perpetuum.Threading.Process; -namespace Perpetuum.Zones +namespace Perpetuum.Services.Weather { public class WeatherService : Process, IWeatherService { - private static readonly byte[] _weatherLookUp = new byte[320]; + private const int FULL_WEATHER = WeatherInfo.WORST_WEATHER - WeatherInfo.BEST_WEATHER + 1; + private static readonly byte[] _weatherLookUp = new byte[FULL_WEATHER]; private readonly TimeRange _updateInterval; - private readonly Observable _observable; + private readonly Observable _observable; private WeatherInfo _current; static WeatherService() { //the full cycle - for (var i = 0; i < _weatherLookUp.Length; i++) + for (var i = WeatherInfo.BEST_WEATHER; i < _weatherLookUp.Length; i++) { - _weatherLookUp[i] = (byte)i.Min(255); + _weatherLookUp[i] = (byte)i.Min(WeatherInfo.WORST_WEATHER); } } @@ -26,19 +27,19 @@ public WeatherService(TimeRange updateInterval) { _updateInterval = updateInterval; _current = GetNextWeather(); - _observable = AnonymousObservable.Create(OnSubscribe); + _observable = new AnonymousObservable(OnSubscribe); } - private void OnSubscribe(IObserver observer) + private void OnSubscribe(IObserver observer) { - observer.OnNext(_current.CreateUpdatePacket()); + observer.OnNext(_current); } private WeatherInfo GetNextWeather() { var duration = FastRandom.NextTimeSpan(_updateInterval); - return _current == null ? new WeatherInfo(GetRandomWeather(),GetRandomWeather(), duration) : + return _current == null ? new WeatherInfo(GetRandomWeather(), GetRandomWeather(), duration) : new WeatherInfo(_current.Next, GetRandomWeather(), duration); } @@ -53,11 +54,10 @@ public override void Update(TimeSpan time) return; _current = GetNextWeather(); - var packet = _current.CreateUpdatePacket(); - _observable.OnNext(packet); + SendWeatherUpdate(_current); } - public IDisposable Subscribe(IObserver observer) + public IDisposable Subscribe(IObserver observer) { return _observable.Subscribe(observer); } @@ -66,5 +66,16 @@ public WeatherInfo GetCurrentWeather() { return _current; } + + private void SendWeatherUpdate(WeatherInfo info) + { + _observable.OnNext(info); + } + + public void SetCurrentWeather(WeatherInfo weather) + { + _current = weather; + SendWeatherUpdate(_current); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs index e758a4912..622c3210f 100644 Binary files a/src/Perpetuum/Threading/LockFree.cs and b/src/Perpetuum/Threading/LockFree.cs differ diff --git a/src/Perpetuum/Threading/ThreadLock.cs b/src/Perpetuum/Threading/ThreadLock.cs new file mode 100644 index 000000000..39bede049 --- /dev/null +++ b/src/Perpetuum/Threading/ThreadLock.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading; + +namespace Perpetuum.Threading +{ + public static class ThreadLockExtensions + { + private sealed class ReadLock : IDisposable + { + private ReaderWriterLockSlim syncLock; + private bool disposed = false; + + public ReadLock(ReaderWriterLockSlim syncLock, TimeSpan timeout) + { + this.syncLock = syncLock ?? throw new ArgumentNullException("syncLock", "Parameter cannot be null"); + + if (!this.syncLock.TryEnterReadLock(timeout)) + { + this.syncLock = null; + throw new TimeoutException("Timed-out waiting for read lock"); + } + } + + public void Dispose() + { + if (!disposed) + { + syncLock?.ExitReadLock(); + syncLock = null; + + disposed = true; + } + } + } + + private sealed class WriteLock : IDisposable + { + private ReaderWriterLockSlim syncLock; + private bool disposed = false; + + public WriteLock(ReaderWriterLockSlim syncLock, TimeSpan timeout) + { + this.syncLock = syncLock ?? throw new ArgumentNullException("syncLock", "Parameter cannot be null"); + + if (!this.syncLock.TryEnterReadLock(timeout)) + { + this.syncLock = null; + throw new TimeoutException("Timed-out waiting for write lock"); + } + } + public void Dispose() + { + if (!disposed) + { + syncLock?.ExitReadLock(); + syncLock = null; + + disposed = true; + } + } + } + + public static IDisposable Read(this ReaderWriterLockSlim obj, TimeSpan timeout) + { + return new ReadLock(obj, timeout); + } + + public static IDisposable Write(this ReaderWriterLockSlim obj, TimeSpan timeout) + { + return new WriteLock(obj, timeout); + } + } +} diff --git a/src/Perpetuum/TimeSpanExtensions.cs b/src/Perpetuum/TimeSpanExtensions.cs index 192c27f2e..bed741717 100644 --- a/src/Perpetuum/TimeSpanExtensions.cs +++ b/src/Perpetuum/TimeSpanExtensions.cs @@ -40,5 +40,27 @@ public static TimeSpan Multiply(this TimeSpan span, double multiplier) { return TimeSpan.FromTicks((long)(span.Ticks * multiplier)); } + + public static string ToHumanTimeString(this TimeSpan span) + { + var format = "g3"; + if (span.TotalMilliseconds < 1000) + { + return $"{span.TotalMilliseconds.ToString(format)} milliseconds"; + } + else if (span.TotalSeconds < 60) + { + return $"{span.TotalSeconds.ToString(format)} seconds"; + } + else if (span.TotalMinutes < 60) + { + return $"{span.TotalMinutes.ToString(format)} minutes"; + } + else if (span.TotalHours < 24) + { + return $"{span.TotalHours.ToString(format)} hours"; + } + return $"{span.TotalDays.ToString(format)} days"; + } } } diff --git a/src/Perpetuum/Timers/TimeKeeper.cs b/src/Perpetuum/Timers/TimeKeeper.cs new file mode 100644 index 000000000..5ceb7fbfd --- /dev/null +++ b/src/Perpetuum/Timers/TimeKeeper.cs @@ -0,0 +1,55 @@ +using System; + +namespace Perpetuum.Timers +{ + /// + /// TimeTracker that uses internal timestamp to measure time passage. + /// Update is no-op, and properties all compute time differences on-call. + /// + public class TimeKeeper : TimeTracker + { + private DateTime _start; + public TimeKeeper(TimeSpan duration) : base(duration) + { + Elapsed = TimeSpan.Zero; + Duration = duration; + Start(); + } + + /// + /// Sets NOW as the start time, use when object creation is not the desired start time + /// + public void Start() + { + SetStart(DateTime.UtcNow); + } + + /// + /// Allow arbitrary starting timestamps + /// + /// UTC time from which to start + public void SetStart(DateTime time) + { + _start = time; + } + + /// + /// No-op override of TimeTracker Update + /// + /// + public override void Update(TimeSpan time) { } + + /// + /// Dynamically computes Elapsed time as difference from UTC now to the start stamp + /// + public override TimeSpan Elapsed + { + get { return DateTime.UtcNow - _start; } + } + + public override void Reset() + { + Start(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Timers/TimeTracker.cs b/src/Perpetuum/Timers/TimeTracker.cs index 900a6f1a5..01c156887 100644 --- a/src/Perpetuum/Timers/TimeTracker.cs +++ b/src/Perpetuum/Timers/TimeTracker.cs @@ -18,13 +18,13 @@ public TimeTracker(TimeSpan duration) Duration = duration; } - public void Update(TimeSpan time) + public virtual void Update(TimeSpan time) { Elapsed += time; } - public TimeSpan Duration { get; private set; } - public TimeSpan Elapsed { get; private set; } + public TimeSpan Duration { get; protected set; } + public virtual TimeSpan Elapsed { get; protected set; } public bool Expired { @@ -36,7 +36,12 @@ public TimeSpan Remaining get { return Duration - Elapsed; } } - public void Reset() + public void Extend(TimeSpan extraDuration) + { + Duration += extraDuration; + } + + public virtual void Reset() { Elapsed = TimeSpan.Zero; } diff --git a/src/Perpetuum/Units/DockingBases/DockingBase.cs b/src/Perpetuum/Units/DockingBases/DockingBase.cs index 4baeb7971..78bd3aad2 100644 --- a/src/Perpetuum/Units/DockingBases/DockingBase.cs +++ b/src/Perpetuum/Units/DockingBases/DockingBase.cs @@ -167,7 +167,7 @@ public void DockIn(Character character,TimeSpan undockDelay) character.ZoneId = null; character.ZonePosition = null; - Transaction.Current.OnCommited(() => JoinChannel(character)); + Transaction.Current.OnCommited(() => TryJoinChannel(character)); } protected IEnumerable GetCharacters() @@ -250,7 +250,7 @@ public Robot CreateStarterRobotForCharacter(Character character,bool setActive = protected virtual bool CanCreateEquippedStartRobot => Zone?.Configuration.Protected ?? false; - public void JoinChannel(Character character) + protected virtual void JoinChannel(Character character) { ChannelManager.JoinChannel(ChannelName,character,ChannelMemberRole.Undefined,null); } @@ -260,6 +260,22 @@ public void LeaveChannel(Character character) ChannelManager.LeaveChannel(ChannelName,character); } + /// + /// Check and joins/leave character in/from the docking base chat, if possible. + /// + /// Character being checked. + public void TryJoinChannel(Character character) + { + if (IsDockingAllowed(character) == ErrorCodes.NoError) + { + JoinChannel(character); + + return; + } + + LeaveChannel(character); + } + public static bool Exists(long baseEid) { return Db.Query().CommandText("select eid from zoneentities where eid=@baseEid").SetParameter("@baseEid", baseEid).ExecuteScalar() > 0 || @@ -271,6 +287,11 @@ public virtual bool IsOnGammaZone() return false; } + public virtual bool IsVisible(Character character) + { + return true; + } + public void AddCentralBank(TransactionType transactionType, double amount) { amount = Math.Abs(amount); diff --git a/src/Perpetuum/Units/DockingBases/DockingBaseHelper.cs b/src/Perpetuum/Units/DockingBases/DockingBaseHelper.cs index 7f72b35ea..8ed1c425b 100644 --- a/src/Perpetuum/Units/DockingBases/DockingBaseHelper.cs +++ b/src/Perpetuum/Units/DockingBases/DockingBaseHelper.cs @@ -3,7 +3,7 @@ using Perpetuum.Containers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Items; +using Perpetuum.Items.Helpers; using Perpetuum.Log; using Perpetuum.Services.ItemShop; using Perpetuum.Services.MarketEngine; diff --git a/src/Perpetuum/Units/TagHelper.cs b/src/Perpetuum/Units/TagHelper.cs index af3dbbd20..ee4ce8723 100644 --- a/src/Perpetuum/Units/TagHelper.cs +++ b/src/Perpetuum/Units/TagHelper.cs @@ -10,10 +10,14 @@ public class TagHelper public void DoTagging(T target,Player tagger,TimeSpan duration) where T:Unit,ITaggable { var currentTagger = target.GetTagger(); + if (currentTagger != null) + { return; + } var builder = target.NewEffectBuilder().SetType(EffectType.effect_tag).SetSource(tagger).WithDuration(duration); + target.ApplyEffect(builder); } @@ -21,10 +25,14 @@ public void DoTagging(T target,Player tagger,TimeSpan duration) where T:Unit, public static Player GetTagger(T tagable) where T:Unit,ITaggable { var zone = tagable.Zone; + if (zone == null) + { return null; + } var tag = tagable.EffectHandler.GetEffectsByType(EffectType.effect_tag).FirstOrDefault(); + return tag?.Source as Player; } } diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index 00475b041..22dd5cc21 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -7,6 +7,9 @@ using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.LandMines; +using Perpetuum.Zones.NpcSystem; namespace Perpetuum.Units { @@ -20,7 +23,7 @@ partial class Unit { private ImmutableDictionary _visibleUnits = ImmutableDictionary.Empty; - protected IReadOnlyCollection GetVisibleUnits() + public IReadOnlyCollection GetVisibleUnits() { return _visibleUnits.Values.ToArray(); } @@ -46,6 +49,16 @@ protected internal virtual void UpdatePlayerVisibility(Player player) // unit => player nem latjak egymast } + protected internal virtual void UpdateUnitVisibility(SentryTurret turret) + { + + } + + protected internal virtual void UpdateUnitVisibility(Npc npc) + { + + } + public virtual void UpdateVisibilityOf(Unit target) { target.UpdateUnitVisibility(this); @@ -57,8 +70,14 @@ protected void UpdateVisibility(Unit target) if (InZone && target.InZone) { - if ( IsDetected(target) ) + if (target.IsPlayer() && (target as Player).HasGMStealth) + { + visibility = Visibility.Invisible; + } + else if (IsDetected(target)) + { visibility = Visibility.Visible; + } } UnitVisibility info; @@ -92,14 +111,22 @@ protected virtual void OnUnitVisibilityUpdated(Unit target, Visibility visibilit protected virtual bool IsDetected(Unit target) { - var robot = target as Robot; - if (robot != null) + double range; + + if (target is LandMine) { - if (robot.IsLocked(this)) + range = (this as Robot).MineDetectionRange; + } else + { + var robot = target as Robot; + if (robot != null && robot.IsLocked(this)) + { return true; + } + + range = 100 / Math.Max(1, target.StealthStrength) * Math.Max(1, DetectionStrength); } - var range = 100 / target.StealthStrength * DetectionStrength; return IsInRangeOf3D(target, range); } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index b99095f26..5f06b0699 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Threading; -using System.Threading.Tasks; -using Perpetuum.Builders; +using Perpetuum.Builders; using Perpetuum.Collections.Spatial; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -17,6 +8,7 @@ using Perpetuum.Modules.Weapons; using Perpetuum.Players; using Perpetuum.Robots; +using Perpetuum.Services.RiftSystem; using Perpetuum.Timers; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -26,9 +18,21 @@ using Perpetuum.Zones.Eggs; using Perpetuum.Zones.Gates; using Perpetuum.Zones.Locking; +using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.PBS; using Perpetuum.Zones.PBS.DockingBases; using Perpetuum.Zones.PBS.Turrets; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Teleporting; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Threading; +using System.Threading.Tasks; namespace Perpetuum.Units { @@ -44,17 +48,19 @@ public SpeedMaxProperty(Unit owner) protected override double CalculateValue() { - var speedMax = _owner.GetPropertyModifier(AggregateField.speed_max); - var speedMaxMod = _owner.GetPropertyModifier(AggregateField.speed_max_modifier); + ItemPropertyModifier speedMax = _owner.GetPropertyModifier(AggregateField.speed_max); + ItemPropertyModifier speedMaxMod = _owner.GetPropertyModifier(AggregateField.speed_max_modifier); speedMaxMod.Modify(ref speedMax); - _owner.ApplyEffectPropertyModifiers(AggregateField.effect_speed_max_modifier,ref speedMax); - _owner.ApplyEffectPropertyModifiers(AggregateField.effect_massivness_speed_max_modifier,ref speedMax); + _owner.ApplyEffectPropertyModifiers(AggregateField.effect_speed_max_modifier, ref speedMax); + _owner.ApplyEffectPropertyModifiers(AggregateField.effect_massivness_speed_max_modifier, ref speedMax); if (_owner.ActualMass > 0) + { speedMax.Multiply(_owner.Mass / _owner.ActualMass); + } - _owner.ApplyEffectPropertyModifiers(AggregateField.effect_speed_highway_modifier,ref speedMax); + _owner.ApplyEffectPropertyModifiers(AggregateField.effect_speed_highway_modifier, ref speedMax); return speedMax.Value; } @@ -74,19 +80,16 @@ protected override bool IsRelated(AggregateField field) } } - + public delegate void UnitEventHandler(Unit unit); public delegate void UnitEventHandler(Unit unit, T args); - public delegate void UnitEventHandler(Unit unit, T1 args1, T2 args2); + public delegate void UnitEventHandler(Unit unit, T1 args1, T2 args2); public abstract partial class Unit : Item { private ICoreRecharger _coreRecharger = CoreRecharger.None; private readonly DamageProcessor _damageProcessor; - - private readonly OptionalPropertyCollection _optionalProperties = new OptionalPropertyCollection(); - private readonly object _killSync = new object(); private Position _currentPosition; @@ -117,18 +120,18 @@ public abstract partial class Unit : Item private ItemProperty _reactorRadiation; private ItemProperty _signatureRadius; private ItemProperty _slope; - + private readonly Lazy _height; protected Unit() { _damageProcessor = new DamageProcessor(this) { DamageTaken = OnDamageTaken }; - var effectHandler = new EffectHandler(this); + EffectHandler effectHandler = new EffectHandler(this); effectHandler.EffectChanged += OnEffectChanged; EffectHandler = effectHandler; - _optionalProperties.PropertyChanged += property => + OptionalProperties.PropertyChanged += property => { UpdateTypes |= UnitUpdateTypes.OptionalProperty; }; @@ -148,23 +151,22 @@ public void SetCoreRecharger(ICoreRecharger recharger) public Guid GetMissionGuid() { - var p = (ReadOnlyOptionalProperty)_optionalProperties.Get(UnitDataType.MissionGuid); + ReadOnlyOptionalProperty p = (ReadOnlyOptionalProperty)OptionalProperties.Get(UnitDataType.MissionGuid); return p?.Value ?? Guid.Empty; } public int GetMissionDisplayOrder() { - var p = (ReadOnlyOptionalProperty)_optionalProperties.Get(UnitDataType.MissionDisplayOrder); - if (p == null) - return -1; - - return p.Value; + ReadOnlyOptionalProperty p = (ReadOnlyOptionalProperty)OptionalProperties.Get(UnitDataType.MissionDisplayOrder); + return p == null ? -1 : p.Value; } public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } private IZone _zone; @@ -174,7 +176,7 @@ public override void AcceptVisitor(IEntityVisitor visitor) public bool InZone => Zone != null; - public OptionalPropertyCollection OptionalProperties => _optionalProperties; + public OptionalPropertyCollection OptionalProperties { get; } = new OptionalPropertyCollection(); public EffectHandler EffectHandler { get; private set; } @@ -185,8 +187,10 @@ public double CurrentSpeed get => _currentSpeed; set { - if ( Math.Abs(_currentSpeed - value) < double.Epsilon ) + if (Math.Abs(_currentSpeed - value) < double.Epsilon) + { return; + } _currentSpeed = value; UpdateTypes |= UnitUpdateTypes.Speed; @@ -198,8 +202,10 @@ public double Direction get => _direction; set { - if ( Math.Abs(_direction - value) < double.Epsilon ) + if (Math.Abs(_direction - value) < double.Epsilon) + { return; + } _direction = value; UpdateTypes |= UnitUpdateTypes.Direction; @@ -211,8 +217,10 @@ public double Orientation get => _orientation; set { - if ( Math.Abs(_orientation - value) < double.Epsilon ) + if (Math.Abs(_orientation - value) < double.Epsilon) + { return; + } _orientation = value; UpdateTypes |= UnitUpdateTypes.Orientation; @@ -223,21 +231,9 @@ public double Orientation public virtual bool IsLockable => !ED.AttributeFlags.NonLockable && !States.Dead && !States.Unlockable; - public virtual ErrorCodes IsAttackable - { - get - { - if ( !ED.AttributeFlags.NonAttackable && !States.Dead) - return ErrorCodes.NoError; + public virtual ErrorCodes IsAttackable => !ED.AttributeFlags.NonAttackable && !States.Dead ? ErrorCodes.NoError : ErrorCodes.TargetIsNonAttackable; - return ErrorCodes.TargetIsNonAttackable; - } - } - - public bool IsInvulnerable - { - get { return ED.AttributeFlags.Invulnerable || EffectHandler.ContainsEffect(EffectType.effect_invulnerable); } - } + public bool IsInvulnerable => ED.AttributeFlags.Invulnerable || EffectHandler.ContainsEffect(EffectType.effect_invulnerable); public bool HasShieldEffect => EffectHandler.ContainsEffect(EffectType.effect_shield); @@ -248,39 +244,39 @@ public bool IsInvulnerable public bool HasDespawnEffect => EffectHandler.ContainsEffect(EffectType.effect_despawn_timer); - public Position WorldPosition - { - get { return Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; } - } + public bool HasNoTeleportWhilePVP => EffectHandler.Effects.Any(e => e.PropertyModifiers.Any(p => p.Field == AggregateField.pvp_no_teleport)); - public Position PositionWithHeight - { - get { return CurrentPosition.AddToZ(Height); } - } + public Position WorldPosition => Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; + + public Position PositionWithHeight => CurrentPosition.AddToZ(Height); public Position CurrentPosition { - get { return _currentPosition; } + get => _currentPosition; set { - var lastPosition = _currentPosition; + Position lastPosition = _currentPosition; _currentPosition = Zone.FixZ(value); UpdateTypes |= UnitUpdateTypes.Position; if (!lastPosition.IsTileChange(_currentPosition)) + { return; + } UpdateTypes |= UnitUpdateTypes.TileChanged; OnTileChanged(); - var lastCellCoord = lastPosition.ToCellCoord(); - var currentCellCoord = _currentPosition.ToCellCoord(); + CellCoord lastCellCoord = lastPosition.ToCellCoord(); + CellCoord currentCellCoord = _currentPosition.ToCellCoord(); if (lastCellCoord == currentCellCoord) + { return; + } OnCellChanged(lastCellCoord, currentCellCoord); } @@ -296,8 +292,10 @@ public Position CurrentPosition /// public void Update(TimeSpan time) { - if ( !InZone || States.Dead ) + if (!InZone || States.Dead) + { return; + } OnUpdate(time); } @@ -306,13 +304,13 @@ public void Update(TimeSpan time) protected virtual void OnUpdate(TimeSpan time) { - _coreRecharger.RechargeCore(this,time); + _coreRecharger.RechargeCore(this, time); EffectHandler.Update(time); UnitUpdatedEventArgs e = null; - _broadcastTimer.Update(time); + _ = _broadcastTimer.Update(time); if (_broadcastTimer.Passed) { @@ -324,28 +322,32 @@ protected virtual void OnUpdate(TimeSpan time) if ((UpdateTypes & UnitUpdateTypes.Unit) > 0) { - var packetBuilder = new UnitUpdatePacketBuilder(this); + UnitUpdatePacketBuilder packetBuilder = new UnitUpdatePacketBuilder(this); OnBroadcastPacket(packetBuilder.ToProxy()); } UpdateTypes = UnitUpdateTypes.None; } - var changedProperties = GetChangedProperties(); + ImmutableHashSet changedProperties = GetChangedProperties(); if (changedProperties != ImmutableHashSet.Empty) { if (e == null) + { e = new UnitUpdatedEventArgs(); + } e.UpdatedProperties = changedProperties; - var builder = new UnitPropertiesUpdatePacketBuilder(this, changedProperties); + UnitPropertiesUpdatePacketBuilder builder = new UnitPropertiesUpdatePacketBuilder(this, changedProperties); OnBroadcastPacket(builder.ToProxy()); } } if (e == null) + { return; + } OnUpdated(e); } @@ -358,34 +360,36 @@ protected virtual void OnBroadcastPacket(IBuilder packetBuilder) BroadcastPacket?.Invoke(this, packetBuilder.Build()); } - protected virtual void OnEffectChanged(Effect effect,bool apply) + protected virtual void OnEffectChanged(Effect effect, bool apply) { EffectChanged?.Invoke(this, effect, apply); - var canBroadcast = effect.Display; + bool canBroadcast = effect.Display; if (canBroadcast) { - var packetBuilder = new EffectPacketBuilder(effect, apply); + EffectPacketBuilder packetBuilder = new EffectPacketBuilder(effect, apply); OnBroadcastPacket(packetBuilder.ToProxy()); } } public void SendRefreshUnitPacket() { - OnBroadcastPacket(UnitEnterPacketBuilder.Create(this,ZoneEnterType.Update).ToProxy()); + OnBroadcastPacket(UnitEnterPacketBuilder.Create(this, ZoneEnterType.Update).ToProxy()); } - public void AddToZone(IZone zone,Position position,ZoneEnterType enterType = ZoneEnterType.Default,IBeamBuilder enterBeamBuilder = null) + public void AddToZone(IZone zone, Position position, ZoneEnterType enterType = ZoneEnterType.Default, IBeamBuilder enterBeamBuilder = null) { _zone = zone; CurrentPosition = zone.FixZ(position); - OnEnterZone(zone,enterType); + OnEnterZone(zone, enterType); zone.AddUnit(this); if (enterBeamBuilder != null) + { zone.CreateBeam(enterBeamBuilder); + } EnterPacketBuilder = UnitEnterPacketBuilder.Create(this, enterType); zone.UpdateUnitRelations(this); @@ -399,16 +403,21 @@ protected virtual void OnEnterZone(IZone zone, ZoneEnterType enterType) { } public void RemoveFromZone(IBeamBuilder exitBeamBuilder = null) { IZone zone; - if ((zone = Interlocked.CompareExchange(ref _zone,null,_zone)) == null) + + if ((zone = Interlocked.CompareExchange(ref _zone, null, _zone)) == null) + { return; + } Debug.Assert(zone != null, "zone != null"); if (exitBeamBuilder != null) + { zone.CreateBeam(exitBeamBuilder); + } + OnBeforeRemovedFromZone(zone); zone.RemoveUnit(this); - OnRemovedFromZone(zone); zone.UpdateUnitRelations(this); RemovedFromZone?.Invoke(this); @@ -416,6 +425,8 @@ public void RemoveFromZone(IBeamBuilder exitBeamBuilder = null) protected virtual void OnRemovedFromZone(IZone zone) { } + protected virtual void OnBeforeRemovedFromZone(IZone zone) { } + public void TakeDamage(DamageInfo damageInfo) { _damageProcessor.TakeDamage(damageInfo); @@ -425,20 +436,22 @@ public void TakeDamage(DamageInfo damageInfo) protected virtual void OnDamageTaken(Unit source, DamageTakenEventArgs e) { - DamageTaken?.Invoke(this,source, e); + DamageTaken?.Invoke(this, source, e); - var packet = new CombatLogPacket(CombatLogType.Damage, this, source); + CombatLogPacket packet = new CombatLogPacket(CombatLogType.Damage, this, source); packet.AppendByte((byte)(e.IsCritical ? 1 : 0)); packet.AppendDouble(e.TotalDamage); packet.AppendDouble(e.TotalKers); packet.Send(this, source); if (!(e.TotalDamage >= 0.0)) + { return; + } Armor -= e.TotalDamage; - OnCombatEvent(source,e); + OnCombatEvent(source, e); if (Armor <= 0.0) { @@ -448,7 +461,7 @@ protected virtual void OnDamageTaken(Unit source, DamageTakenEventArgs e) public virtual void OnCombatEvent(Unit source, CombatEventArgs e) { - + } @@ -471,40 +484,61 @@ private void OnUpdated(UnitUpdatedEventArgs e) protected virtual void DoExplosion() { - var zone = Zone; - if ( zone == null ) + IZone zone = Zone; + if (zone == null) + { return; + } - if ( zone.Configuration.Protected ) + if (zone.Configuration.Protected) + { return; + } - var damageBuilder = GetExplosionDamageBuilder(); - Task.Delay(FastRandom.NextInt(0, 3000)).ContinueWith(t => zone.DoAoeDamage(damageBuilder)); + IDamageBuilder damageBuilder = GetExplosionDamageBuilder(); + _ = Task.Delay(FastRandom.NextInt(0, 3000)).ContinueWith(t => zone.DoAoeDamage(damageBuilder)); + } + + /// + /// Fitted curve to achieve desired explosion distances for bots of certain sizes by class + /// Ensures output will be bounded and that arbitrary input will not result in error or undesired output + /// + /// SignativeRadius of bot + /// Radius in number of tiles (10m) + private double SigRadiusToExplosionRadius(double x) + { + x = x.Clamp(3.0, 40.0); + return (-3.336453 + (1.617863 * x) - (0.09721877 * (x * x)) + (0.002064723 * (x * x * x))).Clamp(1.0, 30.0); } private IDamageBuilder GetExplosionDamageBuilder() { - var damageBuilder = DamageInfo.Builder.WithAttacker(this) - .WithOptimalRange(2) - .WithFalloff(SignatureRadius) - .WithExplosionRadius(SignatureRadius); + double radius = SigRadiusToExplosionRadius(SignatureRadius); + IDamageBuilder damageBuilder = DamageInfo.Builder.WithAttacker(this) + .WithOptimalRange(1) + .WithFalloff(radius) + .WithExplosionRadius(radius); - var armorMaxValue = ArmorMax; + double armorMaxValue = ArmorMax; if (armorMaxValue.IsZero()) + { armorMaxValue = 1.0; + } - var coreMax = CoreMax; + double coreMax = CoreMax; if (coreMax.IsZero()) + { coreMax = 1.0; + } - var damage = (Math.Sin( Core.Ratio(coreMax) * Math.PI) + 1)*(armorMaxValue*0.1); - damageBuilder.WithAllDamageTypes(damage); + double damage = (Math.Sin(Core.Ratio(coreMax) * Math.PI) + 1) * (armorMaxValue * 0.1); + _ = damageBuilder.WithAllDamageTypes(damage); return damageBuilder; } - private class KillDetectorHelper : IEntityVisitor,IEntityVisitor,IEntityVisitor + private class KillDetectorHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor { public KillDetectorHelper() { @@ -537,24 +571,30 @@ public void Visit(PBSObject pbsObject) public void Kill(Unit killer = null) { - if (!Monitor.TryEnter(_killSync) ) + if (!Monitor.TryEnter(_killSync)) + { return; + } try { - var detector = new KillDetectorHelper(); + KillDetectorHelper detector = new KillDetectorHelper(); AcceptVisitor(detector); if (!detector.CanBeKilledResult) + { return; + } if (States.Dead || !InZone) + { return; + } if (killer != null) { - var killingBlowPacket = new CombatLogPacket(CombatLogType.KillingBlow, this, killer); + CombatLogPacket killingBlowPacket = new CombatLogPacket(CombatLogType.KillingBlow, this, killer); killingBlowPacket.Send(this, killer); OnCombatEvent(killer, new KillingBlowEventArgs()); @@ -574,11 +614,11 @@ protected virtual bool CanBeKilled() return true; } - public event Action Dead; + public event Action Dead; protected virtual void OnDead(Unit killer) { - Dead?.Invoke(killer,this); + Dead?.Invoke(killer, this); DoExplosion(); @@ -603,21 +643,23 @@ protected virtual void OnTileChanged() TileChanged?.Invoke(this); } - protected virtual void OnCellChanged(CellCoord lastCellCoord, CellCoord currentCellCoord) {} + protected virtual void OnCellChanged(CellCoord lastCellCoord, CellCoord currentCellCoord) { } public override Dictionary ToDictionary() { - var result = base.ToDictionary(); + Dictionary result = base.ToDictionary(); - if (!InZone) + if (!InZone) + { return result; + } result.Add(k.px, CurrentPosition.X); result.Add(k.py, CurrentPosition.Y); result.Add(k.pz, CurrentPosition.Z); result.Add(k.orientation, (byte)(_orientation * 255)); - var standingControlled = this as IStandingController; + IStandingController standingControlled = this as IStandingController; standingControlled?.AddStandingInfoToDictonary(result); return result; @@ -629,6 +671,15 @@ public virtual void OnAggression(Unit victim) { } + /// + /// Must be called when modules are changed on zone! + /// Handles any internal state dependent on modules being equipped. + /// + public void OnEquipChange() + { + _damageProcessor.OnRequipUnit(); + } + public double GetKersByDamageType(DamageType damageType) { switch (damageType) @@ -643,7 +694,7 @@ public double GetKersByDamageType(DamageType damageType) public double GetResistByDamageType(DamageType damageType) { - var resist = 0.0; + double resist = 0.0; switch (damageType) { @@ -653,14 +704,14 @@ public double GetResistByDamageType(DamageType damageType) case DamageType.Explosive: { resist = _resistExplosive.Value; break; } } - resist /= (resist + 100); + resist /= resist + 100; return resist; } public EffectBuilder NewEffectBuilder() { - var builder = EffectBuilderFactory(); - builder.WithOwner(this); + EffectBuilder builder = EffectBuilderFactory(); + _ = builder.WithOwner(this); return builder; } @@ -676,31 +727,31 @@ public void ApplyPvPEffect() public void ApplyPvPEffect(TimeSpan duration) { - var effect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); - var token = effect?.Token ?? EffectToken.NewToken(); - var builder = NewEffectBuilder(); - builder.SetType(EffectType.effect_pvp).WithDuration(duration).WithToken(token); + Effect effect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); + EffectToken token = effect?.Token ?? EffectToken.NewToken(); + EffectBuilder builder = NewEffectBuilder(); + _ = builder.SetType(EffectType.effect_pvp).WithDuration(duration).WithToken(token); ApplyEffect(builder); } public virtual bool IsWalkable(Vector2 position) { - return Zone.IsWalkable((int) position.X, (int) position.Y, Slope); + return Zone.IsWalkable((int)position.X, (int)position.Y, Slope); } public virtual bool IsWalkable(Position position) { - return Zone.IsWalkable((int) position.X,(int)position.Y, Slope); + return Zone.IsWalkable((int)position.X, (int)position.Y, Slope); } - public virtual bool IsWalkable(int x,int y) + public virtual bool IsWalkable(int x, int y) { - return Zone.IsWalkable(x, y,Slope); + return Zone.IsWalkable(x, y, Slope); } public virtual IDictionary GetDebugInfo() { - var info = new Dictionary + Dictionary info = new Dictionary { {k.eid, Eid}, {k.definitionName, ED.Name}, @@ -710,13 +761,13 @@ public virtual IDictionary GetDebugInfo() }; - var counter = 0; - foreach (var effect in EffectHandler.Effects) + int counter = 0; + foreach (Effect effect in EffectHandler.Effects) { info.Add("e" + counter++, effect.Type.ToString()); } - var standingControlled = this as IStandingController; + IStandingController standingControlled = this as IStandingController; standingControlled?.AddStandingInfoToDictonary(info); return info; @@ -724,7 +775,7 @@ public virtual IDictionary GetDebugInfo() public void ApplyEffectPropertyModifiers(AggregateField modifierField, ref ItemPropertyModifier modifier) { - foreach (var effect in EffectHandler.Effects) + foreach (Effect effect in EffectHandler.Effects) { effect.ApplyTo(ref modifier, modifierField); } @@ -735,7 +786,7 @@ private class UnitEnterPacketBuilder : IBuilder private readonly ZoneEnterType _enterType; private readonly Unit _unit; - private UnitEnterPacketBuilder(Unit unit,ZoneEnterType enterType) + private UnitEnterPacketBuilder(Unit unit, ZoneEnterType enterType) { _unit = unit; _enterType = enterType; @@ -743,10 +794,10 @@ private UnitEnterPacketBuilder(Unit unit,ZoneEnterType enterType) public Packet Build() { - var packet = new Packet(ZoneCommand.EnterUnit); + Packet packet = new Packet(ZoneCommand.EnterUnit); packet.AppendLong(_unit.Eid); - var character = _unit.GetCharacter(); + Accounting.Characters.Character character = _unit.GetCharacter(); packet.AppendInt(character.Id); packet.AppendPosition(_unit.CurrentPosition); @@ -754,7 +805,7 @@ public Packet Build() packet.AppendByte((byte)(_unit.Orientation * byte.MaxValue)); packet.AppendByte((byte)(_unit.Direction * byte.MaxValue)); - var desc = GetDescription(_unit); + byte[] desc = GetDescription(_unit); packet.AppendByteArray(desc); packet.AppendByte((byte)_enterType); _unit.States.AppendToPacket(packet); @@ -763,18 +814,17 @@ public Packet Build() packet.AppendDouble(_unit._speedMax.Value); packet.AppendLong(_unit.Owner); - var robot = _unit as Robot; - if (robot == null) + if (!(_unit is Robot robot)) { packet.AppendByte(0); } else { - var primaryLock = robot.GetPrimaryLock(); + Zones.Locking.Locks.Lock primaryLock = robot.GetPrimaryLock(); if (primaryLock != null) { packet.AppendByte(1); - LockPacketBuilder.AppendTo(primaryLock,packet); + LockPacketBuilder.AppendTo(primaryLock, packet); } else { @@ -783,10 +833,10 @@ public Packet Build() } // effektek - var effects = _unit.EffectHandler.Effects.ToArray(); + Effect[] effects = _unit.EffectHandler.Effects.ToArray(); packet.AppendInt(effects.Length); - foreach (var effect in effects) + foreach (Effect effect in effects) { effect.AppendToStream(packet); } @@ -797,14 +847,13 @@ public Packet Build() private static byte[] GetDescription(Unit unit) { - using (var stream = new MemoryStream()) + using (MemoryStream stream = new MemoryStream()) { - using (var bw = new BinaryWriter(stream)) + using (BinaryWriter bw = new BinaryWriter(stream)) { bw.Write(unit.Definition); - var robot = unit as Robot; - if (robot != null) + if (unit is Robot robot) { WriteRobotComponent(bw, robot.GetRobotComponent()); WriteRobotComponent(bw, robot.GetRobotComponent()); @@ -825,29 +874,29 @@ private static void WriteRobotComponent(BinaryWriter bw, RobotComponent componen if (component == null) { bw.Write(0); - bw.Write((byte) 0); + bw.Write((byte)0); return; } bw.Write(component.Definition); - bw.Write((byte) component.MaxSlots); + bw.Write((byte)component.MaxSlots); - for (var i = 0; i < component.MaxSlots; i++) + for (int i = 0; i < component.MaxSlots; i++) { - var module = component.GetModule(i + 1); + Module module = component.GetModule(i + 1); WriteModule(bw, module); } } private static void WriteModule(BinaryWriter bw, Module module) { - var moduleDefinition = module?.Definition ?? 0; + int moduleDefinition = module?.Definition ?? 0; bw.Write(moduleDefinition); } public static IBuilder Create(Unit unit, ZoneEnterType enterType) { - return new UnitEnterPacketBuilder(unit,enterType); + return new UnitEnterPacketBuilder(unit, enterType); } } @@ -860,44 +909,58 @@ public UnitExitPacketBuilder(Unit unit) _unit = unit; } - private ZoneExitType ExitType - { - get - { - if (_unit.States.Dead) - return ZoneExitType.Died; - - if (_unit.States.Dock) - return ZoneExitType.Docked; - - if (_unit.States.Teleport) - return ZoneExitType.Teleport; - - if (_unit.States.LocalTeleport) - return ZoneExitType.LocalTeleport; - - return ZoneExitType.LeftGrid; - } - } + private ZoneExitType ExitType => _unit.States.Dead + ? ZoneExitType.Died + : _unit.States.Dock + ? ZoneExitType.Docked + : _unit.States.Teleport + ? ZoneExitType.Teleport + : _unit.States.LocalTeleport ? ZoneExitType.LocalTeleport : ZoneExitType.LeftGrid; public Packet Build() { - var packet = new Packet(ZoneCommand.ExitUnit); + Packet packet = new Packet(ZoneCommand.ExitUnit); packet.AppendLong(_unit.Eid); packet.AppendByte((byte)ExitType); return packet; } } - protected bool IsHostile(Unit unit) + protected virtual bool IsHostileFor(Unit unit) { return false; } + + public bool IsHostile(Unit unit) { return unit.IsHostileFor(this); } - protected virtual bool IsHostileFor(Unit unit) { return false; } - internal virtual bool IsHostile(Player player) { return false; } - internal virtual bool IsHostile(AreaBomb bomb) { return false; } - internal virtual bool IsHostile(Gate gate) { return false; } + public virtual bool IsHostile(Player player) { return false; } + + internal virtual bool IsHostile(AreaBomb bomb) { return false; } + + internal virtual bool IsHostile(Gate gate) { return false; } + + internal virtual bool IsHostile(SentryTurret turret) { return false; } + + internal virtual bool IsHostile(IndustrialTurret turret) { return false; } + + internal virtual bool IsHostile(Npc npc) { return false; } + + internal virtual bool IsHostile(CombatDrone drone) { return false; } + + internal virtual bool IsHostile(Rift rift) + { + return false; + } + + internal virtual bool IsHostile(Portal portal) + { + return false; + } + + internal virtual bool IsHostile(MobileTeleport teleport) + { + return false; + } public void StopMoving() { @@ -906,11 +969,8 @@ public void StopMoving() public IEnumerable GetUnitsWithinRange(double distance) where T : Unit { - var zone = Zone; - if (zone == null) - return Enumerable.Empty(); - - return zone.Units.OfType().WithinRange(CurrentPosition, distance); + IZone zone = Zone; + return zone == null ? Enumerable.Empty() : zone.Units.OfType().WithinRange(CurrentPosition, distance); } protected override void OnPropertyChanged(ItemProperty property) @@ -924,92 +984,55 @@ protected override void OnPropertyChanged(ItemProperty property) } } - public double ArmorPercentage - { - get { return Armor.Ratio(ArmorMax); } - } + public double ArmorPercentage => Armor.Ratio(ArmorMax); - public double ArmorMax - { - get { return _armorMax.Value; } - } + public double ArmorMax => _armorMax.Value; public double Armor { - get { return _armor.Value; } - set { _armor.SetValue(value); } + get => _armor.Value; + set => _armor.SetValue(value); } - public double ActualMass - { - get { return _actualMass.Value; } - } + public double ActualMass => _actualMass.Value; - public double CorePercentage - { - get { return Core.Ratio(CoreMax); } - } + public double CorePercentage => Core.Ratio(CoreMax); - public double CoreMax - { - get { return _coreMax.Value; } - } + public double CoreMax => _coreMax.Value; public double Core { - get { return _core.Value; } - set { _core.SetValue(value); } + get => _core.Value; + set => _core.SetValue(value); } - public double CriticalHitChance - { - get { return _criticalHitChance.Value; } - } + public double CriticalHitChance => _criticalHitChance.Value; - public double SignatureRadius - { - get { return _signatureRadius.Value; } - } + public double SignatureRadius => _signatureRadius.Value; - public double SensorStrength - { - get { return _sensorStrength.Value; } - } + public double SensorStrength => _sensorStrength.Value; - public double DetectionStrength - { - get { return _detectionStrength.Value; } - } + public double DetectionStrength => _detectionStrength.Value; - public double StealthStrength - { - get { return _stealthStrength.Value; } - } + public double StealthStrength => _stealthStrength.Value; - public double Massiveness - { - get { return _massiveness.Value; } - } + public double Massiveness => _massiveness.Value; - public double ReactorRadiation - { - get { return _reactorRadiation.Value; } - } + public double ReactorRadiation => _reactorRadiation.Value; - public double Slope - { - get { return _slope.Value; } - } + public double Slope => _slope.Value; public double Speed { get { - var speedMax = _speedMax.Value; + double speedMax = _speedMax.Value; return speedMax * _currentSpeed; } } + public double MaxSpeed => _speedMax.Value; + public TimeSpan CoreRechargeTime => TimeSpan.FromSeconds(_coreRechargeTime.Value); private void InitUnitProperties() @@ -1037,7 +1060,10 @@ private void InitUnitProperties() _core.PropertyChanged += property => { if (property.Value > 1.0) + { return; + } + EffectHandler.RemoveEffectsByCategory(EffectCategory.effcat_zero_core_drop); }; AddProperty(_core); @@ -1114,11 +1140,11 @@ public ArmorProperty(Unit owner) : base(owner, AggregateField.armor_current) { } protected override double CalculateValue() { - var armor = owner.ArmorMax; + double armor = owner.ArmorMax; if (owner.DynamicProperties.Contains(k.armor)) { - var armorPercentage = owner.DynamicProperties.GetOrAdd(k.armor); + double armorPercentage = owner.DynamicProperties.GetOrAdd(k.armor); armor = CalculateArmorByPercentage(armorPercentage); } @@ -1135,7 +1161,7 @@ protected override void OnPropertyChanging(ref double newValue) return; } - var armorMax = owner.ArmorMax; + double armorMax = owner.ArmorMax; if (newValue >= armorMax) { newValue = armorMax; @@ -1145,19 +1171,21 @@ protected override void OnPropertyChanging(ref double newValue) private double CalculateArmorByPercentage(double percent) { if (double.IsNaN(percent)) + { percent = 0.0; + } // 0.0 - 1.0 percent = percent.Clamp(); - var armorMax = owner.ArmorMax; + double armorMax = owner.ArmorMax; if (double.IsNaN(armorMax)) { armorMax = 0.0; } - var val = armorMax * percent; + double val = armorMax * percent; return val; } @@ -1169,7 +1197,7 @@ public CoreProperty(Unit owner) : base(owner, AggregateField.core_current) { } protected override double CalculateValue() { - var currentCore = owner.CoreMax; + double currentCore = owner.CoreMax; if (owner.DynamicProperties.Contains(k.currentCore)) { @@ -1193,8 +1221,8 @@ public ActualMassProperty(Unit owner) : base(owner, AggregateField.mass) { } protected override double CalculateValue() { - var mass = owner.Mass; - var massMod = owner.GetPropertyModifier(AggregateField.mass_modifier); + double mass = owner.Mass; + ItemPropertyModifier massMod = owner.GetPropertyModifier(AggregateField.mass_modifier); massMod.Modify(ref mass); if (owner is Robot robot) @@ -1215,9 +1243,9 @@ public DetectionStrengthProperty(Unit owner) protected override double CalculateValue() { - var v = base.CalculateValue(); + double v = base.CalculateValue(); - var blobableUnit = owner as IBlobableUnit; + IBlobableUnit blobableUnit = owner as IBlobableUnit; blobableUnit?.BlobHandler.ApplyBlobPenalty(ref v, 0.75); return v; @@ -1233,9 +1261,9 @@ public SensorStrengthProperty(Unit owner) protected override double CalculateValue() { - var v = base.CalculateValue(); + double v = base.CalculateValue(); - var blobableUnit = owner as IBlobableUnit; + IBlobableUnit blobableUnit = owner as IBlobableUnit; blobableUnit?.BlobHandler.ApplyBlobPenalty(ref v, 0.5); return v; diff --git a/src/Perpetuum/Units/UnitDespawnHelper.cs b/src/Perpetuum/Units/UnitDespawnHelper.cs index c4e7b33c2..43ea641bd 100644 --- a/src/Perpetuum/Units/UnitDespawnHelper.cs +++ b/src/Perpetuum/Units/UnitDespawnHelper.cs @@ -1,58 +1,86 @@ -using System; using Perpetuum.ExportedTypes; using Perpetuum.Timers; using Perpetuum.Zones.Effects; +using System; namespace Perpetuum.Units { + public interface IUnitDespawnHelper + { + UnitDespawnerCanApplyEffect CanApplyDespawnEffect { set; } + UnitDespawnStrategy DespawnStrategy { set; } + EffectType DespawnEffect { get; } + void Update(TimeSpan time, Unit unit); + } + public delegate bool UnitDespawnerCanApplyEffect(Unit unit); + public delegate void UnitDespawnStrategy(Unit unit); - public class UnitDespawnHelper + public class UnitDespawnHelper : IUnitDespawnHelper { - private readonly TimeSpan _despawnTime; - private readonly IntervalTimer _timer = new IntervalTimer(500); + public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { protected get; set; } + public UnitDespawnStrategy DespawnStrategy { protected get; set; } - private UnitDespawnHelper(TimeSpan despawnTime) + public virtual EffectType DespawnEffect => EffectType.effect_despawn_timer; + + protected readonly TimeSpan _despawnTime; + protected readonly EffectToken _effectToken = EffectToken.NewToken(); + protected readonly IntervalTimer _timer = new IntervalTimer(650); + + protected UnitDespawnHelper(TimeSpan despawnTime) { _despawnTime = despawnTime; } - public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { private get; set; } + protected bool HasEffectOrPending(Unit unit) + { + return unit.EffectHandler.ContainsOrPending(_effectToken); + } - public void Update(TimeSpan time,Unit unit) + public virtual void Update(TimeSpan time, Unit unit) { _timer.Update(time).IsPassed(() => { TryReApplyDespawnEffect(unit); - if (unit.HasDespawnEffect) + if (HasEffectOrPending(unit)) + { return; + } CanApplyDespawnEffect = null; - - unit.States.Teleport = true; //kis villam visual amikor kiszedi - unit.RemoveFromZone(); + if (DespawnStrategy != null) + { + DespawnStrategy(unit); + } + else + { + unit.States.Teleport = true; //kis villam visual amikor kiszedi + unit.RemoveFromZone(); + } }); } - private readonly EffectToken _effectToken = EffectToken.NewToken(); - private void TryReApplyDespawnEffect(Unit unit) { - var canApplyDespawnEffect = CanApplyDespawnEffect; + UnitDespawnerCanApplyEffect canApplyDespawnEffect = CanApplyDespawnEffect; if (canApplyDespawnEffect == null) + { return; + } - var applyDespawnEffect = canApplyDespawnEffect(unit); + bool applyDespawnEffect = canApplyDespawnEffect(unit); if (!applyDespawnEffect) + { return; + } ApplyDespawnEffect(unit); } - private void ApplyDespawnEffect(Unit unit) + protected void ApplyDespawnEffect(Unit unit) { - var effectBuilder = unit.NewEffectBuilder().SetType(EffectType.effect_despawn_timer).WithDuration(_despawnTime).WithToken(_effectToken); + EffectBuilder effectBuilder = unit.NewEffectBuilder().SetType(DespawnEffect).WithDuration(_despawnTime).WithToken(_effectToken); unit.ApplyEffect(effectBuilder); } @@ -61,12 +89,50 @@ public override string ToString() return $"DespawnTime: {_despawnTime}"; } - public static UnitDespawnHelper Create(Unit unit,TimeSpan despawnTime) + public static UnitDespawnHelper Create(Unit unit, TimeSpan despawnTime) { - var helper = new UnitDespawnHelper(despawnTime); + UnitDespawnHelper helper = new UnitDespawnHelper(despawnTime); helper.ApplyDespawnEffect(unit); + return helper; } + } + + public class CancellableDespawnHelper : UnitDespawnHelper + { + public override EffectType DespawnEffect => EffectType.effect_stronghold_despawn_timer; + private CancellableDespawnHelper(TimeSpan despawnTime) : base(despawnTime) { } + private bool _canceled = false; + public void Cancel(Unit unit) + { + _canceled = true; + RemoveDespawnEffect(unit); + } + + private void RemoveDespawnEffect(Unit unit) + { + unit.EffectHandler.RemoveEffectByToken(_effectToken); + } + + public override void Update(TimeSpan time, Unit unit) + { + _timer.Update(time).IsPassed(() => + { + if (_canceled || HasEffectOrPending(unit)) + { + return; + } + + DespawnStrategy?.Invoke(unit); + }); + } + + public static new CancellableDespawnHelper Create(Unit unit, TimeSpan despawnTime) + { + CancellableDespawnHelper helper = new CancellableDespawnHelper(despawnTime); + helper.ApplyDespawnEffect(unit); + return helper; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Units/UnitExtensions.cs b/src/Perpetuum/Units/UnitExtensions.cs index 24f8af8f3..260e36c4a 100644 --- a/src/Perpetuum/Units/UnitExtensions.cs +++ b/src/Perpetuum/Units/UnitExtensions.cs @@ -1,12 +1,13 @@ -using System.Collections.Generic; -using System.Linq; using Perpetuum.ExportedTypes; using Perpetuum.Log; using Perpetuum.Players; using Perpetuum.Players.ExtensionMethods; using Perpetuum.Zones; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using Perpetuum.Zones.PBS; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Units { @@ -21,23 +22,25 @@ public static void AddDirectThreat(this Unit unit, Unit hostile, double threatVa unit.UpdateVisibilityOf(hostile); } - unit.AddThreat(hostile,new Threat(ThreatType.Direct, threatValue)); + unit.AddThreat(hostile, new Threat(ThreatType.Direct, threatValue)); } - public static void AddThreat(this Unit unit,Unit hostile,Threat threat) + public static void AddThreat(this Unit unit, Unit hostile, Threat threat) { - if (unit is Npc npc && npc.CanAddThreatTo(hostile, threat)) - npc.AddThreat(hostile, threat, true); + if (unit is SmartCreature smartCreature && smartCreature.CanAddThreatTo(hostile, threat)) + { + smartCreature.AddThreat(hostile, threat, true); + } } - public static bool TryGetConstructionRadius(this Unit unit,out int radius) + public static bool TryGetConstructionRadius(this Unit unit, out int radius) { radius = 0; if (unit?.ED.Config.constructionRadius == null) return false; - radius = (int) unit.ED.Config.constructionRadius; + radius = (int)unit.ED.Config.constructionRadius; return true; } @@ -51,7 +54,7 @@ public static int GetItemWorkRangeOrDefault(this Unit unit) if (unit == null || unit.ED.Config.item_work_range == null) return 0; - return (int) unit.ED.Config.item_work_range; + return (int)unit.ED.Config.item_work_range; } @@ -70,7 +73,7 @@ public static int GetCycleTimeMs(this Unit unit) return 0; if (unit.ED.Config.cycle_time != null) - return (int) unit.ED.Config.cycle_time; + return (int)unit.ED.Config.cycle_time; return 30000; } @@ -92,7 +95,7 @@ public static int GetConstructionRadius(this Unit unit) return 0; if (unit.ED.Config.constructionRadius != null) - return (int) unit.ED.Config.constructionRadius; + return (int)unit.ED.Config.constructionRadius; Logger.Error("consistency error. no construction radius was defined for definition: " + unit.Definition + " " + unit.ED.Name); return 10; @@ -100,7 +103,7 @@ public static int GetConstructionRadius(this Unit unit) - public static void AddEffectsDebugInfo(this Unit unit, IDictionary info) + public static void AddEffectsDebugInfo(this Unit unit, IDictionary info) { var effects = new Dictionary(); var counter = 0; @@ -120,9 +123,9 @@ public static IDictionary GetMiniDebugInfo(this Unit unit) return new Dictionary { {k.name, unit.Name}, - {k.eid, unit.Eid}, - {k.definitionName, unit.ED.Name}, - {k.owner, unit.Owner}, + {k.eid, unit.Eid}, + {k.definitionName, unit.ED.Name}, + {k.owner, unit.Owner}, {k.state, unit.States.ToString()} }; } @@ -133,7 +136,7 @@ public static int GetTransmitRadius(this Unit unit) return 0; if (unit.ED.Config.transmitradius != null) - return (int) unit.ED.Config.transmitradius; + return (int)unit.ED.Config.transmitradius; Logger.Error("consistency error. no transmitRadius was defined for definition: " + unit.Definition + " " + unit.ED.Name); return 0; @@ -150,20 +153,20 @@ public static double GetCoreTransferred(this Unit unit) Logger.Error("coreTransferred not defined for " + unit); return 100; - + } public static double GetTransferEfficiency(this Unit unit) { if (unit.ED.Config.transferEfficiency != null) - return ((double) unit.ED.Config.transferEfficiency).Clamp(); + return ((double)unit.ED.Config.transferEfficiency).Clamp(); Logger.Error("transferEfficiency not defined for " + unit); return 0.8; } - public static void KillAll(this IEnumerable units,Unit killer = null) + public static void KillAll(this IEnumerable units, Unit killer = null) { foreach (var unit in units) { @@ -171,22 +174,29 @@ public static void KillAll(this IEnumerable units,Unit killer = null) } } - public static void SpreadAssistThreatToNpcs(this Unit unit, Unit assistant,Threat threat) + public static void SpreadAssistThreatToNpcs(this Unit unit, Unit assistant, Threat threat) { - if ( unit == null || assistant == null) + if (unit == null || assistant == null) + { return; + } - foreach (var npc in unit.GetWitnessUnits()) + foreach (var smartCreature in unit.GetWitnessUnits()) { - npc.AddAssistThreat(assistant,unit,threat); + smartCreature.AddAssistThreat(assistant, unit, threat); } } - + public static bool IsPlayer(this Unit unit) { return unit is Player; } public static void SendPacketToWitnessPlayers(this Unit source, Packet packet, bool sendSelf = false) { + if (source == null) + { + return; + } + if (sendSelf) { (source as Player)?.Session.SendPacket(packet); @@ -196,12 +206,12 @@ public static void SendPacketToWitnessPlayers(this Unit source, Packet packet, b } - public static IEnumerable WithinRange(this IEnumerable units,Position position,double distance) where T : Unit + public static IEnumerable WithinRange(this IEnumerable units, Position position, double distance) where T : Unit { return units.Where(unit => unit.IsInRangeOf3D(position, distance)); } - public static IEnumerable WithinRange2D(this IEnumerable units,Position position,double distance) where T : Unit + public static IEnumerable WithinRange2D(this IEnumerable units, Position position, double distance) where T : Unit { return units.Where(unit => unit.CurrentPosition.IsInRangeOf2D(position, distance)); } @@ -235,7 +245,7 @@ public static T GetNearestUnit(this IEnumerable units, Position position) return nearestUnit; } - public static IEnumerable GetAllByCategoryFlags(this IEnumerable units, CategoryFlags cf) where T:Unit + public static IEnumerable GetAllByCategoryFlags(this IEnumerable units, CategoryFlags cf) where T : Unit { return units.Where(u => u.ED.CategoryFlags.IsCategory(cf)).ToArray(); } diff --git a/src/Perpetuum/ValueTypeExtensions.cs b/src/Perpetuum/ValueTypeExtensions.cs index 3c22c694a..53f654afd 100644 --- a/src/Perpetuum/ValueTypeExtensions.cs +++ b/src/Perpetuum/ValueTypeExtensions.cs @@ -75,6 +75,12 @@ public static bool IsInRange(this double num, double minRange, double maxRange) return num >= minRange && num <= maxRange; } + [Pure, UsedImplicitly] + public static bool IsInRange(this int num, int minRange, int maxRange) + { + return num >= minRange && num <= maxRange; + } + public static bool IsZero(this double num) { return Math.Abs(num) < double.Epsilon; @@ -85,6 +91,18 @@ public static bool IsZero(this float num) return Math.Abs(num) < float.Epsilon; } + public static bool IsApproximatelyEqual(this double left, double right, double precision = 0.0000001) + { + var result = Math.Abs(left - right); + return result <= precision; + } + + public static bool IsApproximatelyEqual(this float left, float right, float precision = 0.0000001f) + { + var result = Math.Abs(left - right); + return result <= precision; + } + public static double Clamp(this double value, double lowerBound = 0.0, double upperBound = 1.0) { return Math.Min(upperBound, Math.Max(lowerBound, value)); diff --git a/src/Perpetuum/Wallets/IWallet.cs b/src/Perpetuum/Wallets/IWallet.cs index aa7643bb5..98b2578d6 100644 Binary files a/src/Perpetuum/Wallets/IWallet.cs and b/src/Perpetuum/Wallets/IWallet.cs differ diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs index fc0bae2ee..d3229dc4e 100644 Binary files a/src/Perpetuum/Wallets/Wallet.cs and b/src/Perpetuum/Wallets/Wallet.cs differ diff --git a/src/Perpetuum/Zones/Artifacts/Generators/Loot/ArtifactLootGenerator.cs b/src/Perpetuum/Zones/Artifacts/Generators/Loot/ArtifactLootGenerator.cs index 5984afea2..530fff51d 100644 --- a/src/Perpetuum/Zones/Artifacts/Generators/Loot/ArtifactLootGenerator.cs +++ b/src/Perpetuum/Zones/Artifacts/Generators/Loot/ArtifactLootGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Perpetuum.Services.Looting; @@ -8,10 +9,12 @@ namespace Perpetuum.Zones.Artifacts.Generators.Loot public class ArtifactLootGenerator : IArtifactLootGenerator { private readonly IArtifactRepository _artifactRepository; + private Random _random; public ArtifactLootGenerator(IArtifactRepository artifactRepository) { _artifactRepository = artifactRepository; + _random = new Random(); } public ArtifactLootItems GenerateLoot(Artifact artifact) @@ -32,7 +35,8 @@ public ArtifactLootItems GenerateLoot(Artifact artifact) { foreach (var loot in loots) { - var chance = FastRandom.NextDouble(); + var chance = _random.NextDouble(); + if (chance > loot.Chance) continue; diff --git a/src/Perpetuum/Zones/Blobs/BlobEmitters/BlobEmitterUnit.cs b/src/Perpetuum/Zones/Blobs/BlobEmitters/BlobEmitterUnit.cs index 4972b3c28..ba71ab0f1 100644 --- a/src/Perpetuum/Zones/Blobs/BlobEmitters/BlobEmitterUnit.cs +++ b/src/Perpetuum/Zones/Blobs/BlobEmitters/BlobEmitterUnit.cs @@ -4,15 +4,17 @@ namespace Perpetuum.Zones.Blobs.BlobEmitters { - public class BlobEmitterUnit : Unit,IBlobEmitter + public class BlobEmitterUnit : Unit, IBlobEmitter { private double _blobEmission; private double _blobEmissionRadius; private UnitDespawnHelper _despawnHelper; + public bool IsPlayerSpawned => Owner != 0; + public TimeSpan DespawnTime { - set { _despawnHelper = UnitDespawnHelper.Create(this,value); } + set { _despawnHelper = UnitDespawnHelper.Create(this, value); } } public override bool IsLockable diff --git a/src/Perpetuum/Zones/DamageProcessors/DamageProcessor.cs b/src/Perpetuum/Zones/DamageProcessors/DamageProcessor.cs index 946b564d3..090c44b86 100644 --- a/src/Perpetuum/Zones/DamageProcessors/DamageProcessor.cs +++ b/src/Perpetuum/Zones/DamageProcessors/DamageProcessor.cs @@ -13,7 +13,7 @@ namespace Perpetuum.Zones.DamageProcessors public class DamageProcessor { private readonly Unit _unit; - private readonly Lazy _shield; + private Lazy _shield; private readonly Queue _damageInfos = new Queue(); private bool _processing; @@ -22,9 +22,14 @@ public class DamageProcessor public DamageProcessor(Unit unit) { _unit = unit; + OnRequipUnit(); + } + + public void OnRequipUnit() + { _shield = new Lazy(() => { - var robot = unit as Robot; + var robot = _unit as Robot; return robot?.Modules.OfType().FirstOrDefault(); }); } diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 6937b07a5..e31b53fda 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -5,11 +5,11 @@ namespace Perpetuum.Zones public class DistanceConstants { public const double TERRAIN_DEGRADE_DISTANCE_FROM_PBS = 75.0; - + public const double KIOSK_USE_DISTANCE = 3.0; public const double SWITCH_USE_DISTANCE = 3.0; public const double ITEMSUPPLY_USE_DISTANCE = 3.0; - + public const double ALARMSWITCH_STAYCLOSE_DISTANCE = 14.0; public const double ITEM_SHOP_DISTANCE = 3.0; public const double AREA_BOMB_DISTANCE_TO_STATIONS = 90.0; @@ -19,17 +19,23 @@ public class DistanceConstants public const double MOBILE_TELEPORT_USE_RANGE = 8.0; public const double INTRUSION_SAP_DEFENSE_RANGE = 100.0; public const double PLANT_MIN_DISTANCE_FROM_SAP = 40; - + public const double MAX_TERRAFORM_LEVEL_DIFFERENCE = 8.0; public const double MAX_TERRAFORM_ALTITUDE_PLAYER_VS_TARGET_DIFFERENCE = 20.0; public const double MOBILE_WORLD_TELEPORT_RANGE = 3072; - public const double PROXIMITY_PROBE_DEPLOY_RANGE = 100.0; + public const double PROXIMITY_PROBE_DEPLOY_RANGE_FROM_BASE = 100.0; + public const double PROXIMITY_PROBE_DEPLOY_RANGE_FROM_TELEPORT = 100.0; + public const double PROXIMITY_PROBE_DEPLOY_RANGE_FROM_PROBE = 100.0; public const double PLANT_MIN_DISTANCE_FROM_BASE = 100.0; public const double PLANT_MAX_DISTANCE_FROM_OUTPOST = 300.0; + public const double LANDMINE_DEPLOY_RANGE_FROM_BASE = 100.0; + public const double LANDMINE_DEPLOY_RANGE_FROM_TELEPORT = 100.0; + public const double LANDMINE_DEPLOY_RANGE_FROM_LANDMINE = 5.0; + public const double PLANT_MIN_DISTANCE_FROM_PBS = 5.0; public const double PLANT_MAX_DISTANCE_FROM_PBS = 150.0; - + public const double NPC_EGG_DISTANCE_ON_PVE_ZONES = 50.0; public const double PBS_NODE_USE_DISTANCE = 10.0; public const double TERRAFORM_MIN_RANGE_FROM_OBJECTS = 25.0; @@ -46,18 +52,26 @@ public class DistanceConstants public const double MISSION_RANDOM_POINT_FINDRADIUS_DEFAULT = 100; public const double MAX_NPC_FLOCK_HOME_RANGE = 40; + public const double PBS_DIST_FROM_NPC_BASE = 300; + public const double GAMMA_BOMB_STACK_DISTANCE = 5.0; + + public const double PBS_DIST_FROM_TELEPORT = 200; + + public const double RCU_DEPLOY_RANGE_FROM_BASE = 100.0; + public const double RCU_DEPLOY_RANGE_FROM_TELEPORT = 100.0; + public const double RCU_DEPLOY_RANGE_FROM_RCU = 2.0; public static Dictionary GetEnumDictionary() { - var result = new Dictionary(); + Dictionary result = new Dictionary(); - var type = typeof (DistanceConstants); // Get type pointer - var fields = type.GetFields(); // Obtain all fields - foreach (var field in fields) // Loop through fields + System.Type type = typeof(DistanceConstants); // Get type pointer + System.Reflection.FieldInfo[] fields = type.GetFields(); // Obtain all fields + foreach (System.Reflection.FieldInfo field in fields) // Loop through fields { - var name = field.Name; // Get string name - var temp = field.GetValue(null); // Get value - + string name = field.Name; // Get string name + object temp = field.GetValue(null); // Get value + if (temp is double) { result.Add(name, temp); diff --git a/src/Perpetuum/Zones/Effects/AuraEffect.cs b/src/Perpetuum/Zones/Effects/AuraEffect.cs index d9beadaa8..4be9aa5bb 100644 --- a/src/Perpetuum/Zones/Effects/AuraEffect.cs +++ b/src/Perpetuum/Zones/Effects/AuraEffect.cs @@ -65,7 +65,7 @@ private void ApplyEffectToTargets(IZone zone) foreach (var unit in units) { - if ( unit == Owner ) + if (unit == Owner) continue; if ( unit.EffectHandler.ContainsToken(Token) ) diff --git a/src/Perpetuum/Zones/Effects/EffectHandler.cs b/src/Perpetuum/Zones/Effects/EffectHandler.cs index 1cb89941f..f89d7400c 100644 --- a/src/Perpetuum/Zones/Effects/EffectHandler.cs +++ b/src/Perpetuum/Zones/Effects/EffectHandler.cs @@ -178,6 +178,11 @@ public void RemoveEffectByToken(EffectToken token) Remove(effect); } + public bool ContainsOrPending(EffectToken token) + { + return ContainsToken(token) || _newEffects.Any(e => e.Token == token); + } + public bool ContainsToken(EffectToken token) { return Effects.Any(e => e.Token == token); diff --git a/src/Perpetuum/Zones/Effects/GangEffect.cs b/src/Perpetuum/Zones/Effects/GangEffect.cs index 520782e4d..c49458590 100644 --- a/src/Perpetuum/Zones/Effects/GangEffect.cs +++ b/src/Perpetuum/Zones/Effects/GangEffect.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; using Perpetuum.Groups.Gangs; using Perpetuum.Players; using Perpetuum.Units; +using Perpetuum.Zones.RemoteControl; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Zones.Effects { @@ -14,13 +16,20 @@ protected override void OnTick() { if (Owner != Source) { - var gang = ((Player)Owner).Gang; - // ha nincs mar gangben vagy a forras mar nincs gangben + Gang gang = Owner is Player player + ? player.Gang + : Owner is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer.Gang + : null; + if (gang == null || !gang.IsMember((Player)Source)) { OnRemoved(); + return; } + } base.OnTick(); @@ -28,8 +37,12 @@ protected override void OnTick() protected override IEnumerable GetTargets(IZone zone) { - var player = (Player)Owner; - return zone.GetGangMembers(player.Gang).WithinRange(Owner.CurrentPosition, Radius); + Player player = (Player)Owner; + IEnumerable gangMembers = zone.GetGangMembers(player.Gang); + IEnumerable alliedTurrets = zone.GetAlliedTurretsByPlayers(new[] { player }.Union(gangMembers)); + IEnumerable alliedUnits = gangMembers.Union(alliedTurrets); + + return alliedUnits.WithinRange(Owner.CurrentPosition, Radius); } } diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs new file mode 100644 index 000000000..45ba99b84 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs @@ -0,0 +1,28 @@ +using Perpetuum.Units; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + /// + /// Handles application of any/all ZoneEffects + /// + public interface IZoneEffectHandler + { + /// + /// Initializes all ZoneEffects on a unit entering the zone + /// + /// Unit entering zone + void OnEnterZone(Unit unit); + + /// + /// Method to remove active ZoneEffect on the zone + /// + /// ZoneEffect to remove + void RemoveEffect(ZoneEffect zoneEffect); + + /// + /// Method to add ZoneEffect to the zone + /// + /// ZoneEffect to add + void AddEffect(ZoneEffect zoneEffect); + } +} diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectRepository.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectRepository.cs new file mode 100644 index 000000000..74e8f45ab --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectRepository.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + /// + /// Interface to lookup ZoneEffects by zone + /// + public interface IZoneEffectRepository + { + List GetZoneEffects(IZone zone); + } +} diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs new file mode 100644 index 000000000..8dd03b439 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs @@ -0,0 +1,49 @@ +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + /// + /// An effect to be applied to everyone on a give zone + /// + public class ZoneEffect + { + public int ZoneId { get; private set; } + public EffectType Effect { get; private set; } + public bool PlayerOnly { get; private set; } + + public ZoneEffect(int zoneId, EffectType effectType, bool forPlayersOnly) + { + ZoneId = zoneId; + Effect = effectType; + PlayerOnly = forPlayersOnly; + } + + public override bool Equals(object obj) + { + if (obj is ZoneEffect z) + { + return Equals(z); + } + return base.Equals(obj); + } + + public bool Equals(ZoneEffect other) + { + return other != null && + ZoneId == other.ZoneId && + Effect == other.Effect && + PlayerOnly == other.PlayerOnly; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = ZoneId * 31; + hashCode *= 31 + Effect.GetHashCode(); + hashCode *= 31 + PlayerOnly.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs new file mode 100644 index 000000000..8b80c3025 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs @@ -0,0 +1,91 @@ +using Perpetuum.Log; +using Perpetuum.Players; +using Perpetuum.Units; +using Perpetuum.Zones.RemoteControl; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + public class ZoneEffectHandler : IZoneEffectHandler + { + private readonly IZone _zone; + private readonly ConcurrentDictionary _effects; + + public ZoneEffectHandler(IZone zone) + { + _zone = zone; + _effects = new ConcurrentDictionary(); + InitCollection(); + } + + private void InitCollection() + { + foreach (var zoneEffect in ZoneEffectReader.GetStaticZoneEffects(_zone)) + { + _effects.Add(zoneEffect, byte.MinValue); + } + } + + public void AddEffect(ZoneEffect zoneEffect) + { + if (zoneEffect == null) return; + + _effects.TryAdd(zoneEffect, byte.MinValue); + OnZoneEffectAdded(zoneEffect); + } + + public void RemoveEffect(ZoneEffect zoneEffect) + { + if (zoneEffect == null) return; + + _effects.TryRemove(zoneEffect, out byte b); + OnZoneEffectRemoved(zoneEffect); + } + + private void OnZoneEffectRemoved(ZoneEffect effect) + { + var eligibleUnits = _zone.Units.Where(u => u.EffectHandler.ContainsEffect(effect.Effect)); + foreach (var unit in eligibleUnits) + { + unit.EffectHandler.RemoveEffectsByType(effect.Effect); + Logger.DebugInfo($"Removing {effect.Effect} to {unit} on zone:{_zone.Id}"); + } + } + + private void OnZoneEffectAdded(ZoneEffect effect) + { + var eligibleUnits = _zone.Units.Where(u => CanApplyEffect(effect)(u)); + foreach (var unit in eligibleUnits) + { + var builder = unit.NewEffectBuilder().SetType(effect.Effect).SetOwnerToSource(); + unit.ApplyEffect(builder); + Logger.DebugInfo($"Adding {effect.Effect} to {unit} on zone:{_zone.Id}"); + } + } + + private IEnumerable GetEffects() + { + return _effects.Keys; + } + + private Func CanApplyEffect(ZoneEffect effect) + { + return u => !u.EffectHandler.ContainsEffect(effect.Effect) && (!effect.PlayerOnly || u is Player || u is RemoteControlledCreature); + } + + public void OnEnterZone(Unit unit) + { + foreach (var zoneEffect in GetEffects()) + { + if (CanApplyEffect(zoneEffect)(unit)) + { + var builder = unit.NewEffectBuilder().SetType(zoneEffect.Effect).SetOwnerToSource(); + unit.ApplyEffect(builder); + } + } + } + } +} diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs new file mode 100644 index 000000000..46b9b4473 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs @@ -0,0 +1,43 @@ +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + /// + /// DB querying object for ZoneEffects + /// + public static class ZoneEffectReader + { + private static ZoneEffect CreateZoneEffectFromRecord(IDataRecord record) + { + try + { + var zoneId = record.GetValue("zoneid"); + var effectId = record.GetValue("effectid"); + var effectType = EnumHelper.GetEnum(effectId); + var config = new ZoneEffect(zoneId, effectType, true); // TODO new bool col for isPlayerOnly + return config; + } + catch (Exception ex) + { + Logger.Exception(ex); + } + return null; + } + + public static IEnumerable GetStaticZoneEffects(IZone zone) + { + var zoneEffects = Db.Query().CommandText("SELECT zoneid, effectid FROM zoneeffects WHERE zoneid=@zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute() + .Select(CreateZoneEffectFromRecord); + + return zoneEffects.Where(z => z != null); + } + } +} diff --git a/src/Perpetuum/Zones/Eggs/AreaBomb.cs b/src/Perpetuum/Zones/Eggs/AreaBomb.cs index 197642426..fad7c7da8 100644 --- a/src/Perpetuum/Zones/Eggs/AreaBomb.cs +++ b/src/Perpetuum/Zones/Eggs/AreaBomb.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Modules.Weapons; @@ -18,6 +19,11 @@ public override void AcceptVisitor(IEntityVisitor visitor) if (!TryAcceptVisitor(this, visitor)) base.AcceptVisitor(visitor); } + public override void Initialize() + { + DespawnTime = TimeSpan.FromMinutes(5); + base.Initialize(); + } protected override void OnSummonSuccess(IZone zone, Player[] summoners) { @@ -45,6 +51,7 @@ protected override void OnSummonSuccess(IZone zone, Player[] summoners) .WithDamage(DamageType.Explosive, ED.Config.damage_explosive ?? 0.0) .WithDamage(DamageType.Kinetic, ED.Config.damage_kinetic ?? 0.0) .WithDamage(DamageType.Thermal, ED.Config.damage_thermal ?? 0.0) + .WithDamage(DamageType.Toxic, ED.Config.damage_toxic ?? 0.0) .WithOptimalRange(2) .WithFalloff(ED.Config.item_work_range ?? 0.0) .WithExplosionRadius(ED.Config.explosion_radius ?? 0.0); diff --git a/src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableAroundPositionFinder.cs b/src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableAroundPositionFinder.cs new file mode 100644 index 000000000..e7f8fe786 --- /dev/null +++ b/src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableAroundPositionFinder.cs @@ -0,0 +1,63 @@ +using Perpetuum.Units; + +namespace Perpetuum.Zones.Finders.PositionFinders +{ + /// + /// Find a Random walkable location within some defined circle + /// + public class RandomWalkableAroundPositionFinder : ZonePositionFinder + { + protected const int AREA_TEST_SIZE = 3000; + protected const int MAX_INTERATIONS = 100; + + protected readonly int _maxRange; + protected readonly double _slope = 4.0; + protected readonly Position _origin; + protected readonly Area _zoneArea; + + public RandomWalkableAroundPositionFinder(IZone zone, Position origin, int range, Unit unit) : this(zone, origin, range, unit.Slope) { } + public RandomWalkableAroundPositionFinder(IZone zone, Position origin, int range, double slope = 4.0) : base(zone) + { + _slope = slope; + _origin = origin; + _maxRange = range; + _zoneArea = zone.Size.ToArea(); + } + + protected Position FindClosestWalkable(IZone zone, Position pos) + { + var finder = new ClosestWalkablePositionFinder(zone, pos, _slope); + if(finder.Find(out Position p)) + { + return p; + } + return Position.Empty; + } + + protected virtual bool CheckResult(IZone zone, Position position) + { + return zone.FindWalkableArea(position, _zoneArea, AREA_TEST_SIZE) != null; + } + + protected virtual Position GetRandomPos(IZone zone) + { + return zone.FindPassablePointInRadius(_origin, _maxRange); + } + + protected override bool Find(IZone zone, out Position result) + { + result = Position.Empty; + for (int i = 0; i < MAX_INTERATIONS; i++) + { + var randPos = GetRandomPos(zone); + var walkablePos = FindClosestWalkable(zone, randPos); + if (CheckResult(zone, walkablePos)) + { + result = walkablePos; + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableOnCircle.cs b/src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableOnCircle.cs new file mode 100644 index 000000000..f2cc72bf9 --- /dev/null +++ b/src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableOnCircle.cs @@ -0,0 +1,29 @@ +using Perpetuum.Units; + +namespace Perpetuum.Zones.Finders.PositionFinders +{ + /// + /// Finds a random position on the edge of a circle, with some tolerance + /// + public class RandomWalkableOnCircle : RandomWalkableAroundPositionFinder + { + private readonly int _edgeTolerance; + public RandomWalkableOnCircle(IZone zone, Position origin, int range, int edgeTolerance, Unit unit) : this(zone, origin, range, edgeTolerance, unit.Slope) { } + public RandomWalkableOnCircle(IZone zone, Position origin, int range, int edgeTolerance, double slope = 4.0) : base(zone, origin, range, slope) + { + _edgeTolerance = edgeTolerance; + } + + protected override Position GetRandomPos(IZone zone) + { + var random = FastRandom.NextDouble(0.0, 1.0); + return _origin.OffsetInDirection(random, _maxRange); + } + + protected override bool CheckResult(IZone zone, Position position) + { + var check = zone.FindWalkableArea(position, _zoneArea, AREA_TEST_SIZE) != null; + return check && position.TotalDistance2D(_origin) > _maxRange - _edgeTolerance; + } + } +} diff --git a/src/Perpetuum/Zones/Gates/Gate.cs b/src/Perpetuum/Zones/Gates/Gate.cs index 83f9c90cf..5e0360de0 100644 --- a/src/Perpetuum/Zones/Gates/Gate.cs +++ b/src/Perpetuum/Zones/Gates/Gate.cs @@ -10,6 +10,7 @@ using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; +using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.PBS; using Perpetuum.Zones.Terrains; @@ -193,7 +194,7 @@ private void OpenOrClose(IZone zone, bool open) else { bi.Obstacle = true; - bi.Height = 14; + // bi.Height = 14; // original blocking behaviour } using (new TerrainUpdateMonitor(zone)) @@ -246,5 +247,24 @@ protected override bool IsHostileFor(Unit unit) { return unit.IsHostile(this); } + + protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) + { + base.OnDamageTaken(source, e); + if(source is Player) + { + source.ApplyPvPEffect(); + } + } + + public bool IsVisible(Character character) + { + Corporation.GetCorporationEidAndRoleFromSql(character, out long corporationEid, out CorporationRole role); + if (Owner == corporationEid) + { + return role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO, CorporationRole.viewPBS); + } + return false; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/HarvestLogHandler.cs b/src/Perpetuum/Zones/HarvestLogHandler.cs new file mode 100644 index 000000000..6eae0a90a --- /dev/null +++ b/src/Perpetuum/Zones/HarvestLogHandler.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Perpetuum.Data; +using Perpetuum.Log; +using Perpetuum.Timers; + +namespace Perpetuum.Zones +{ + public class HarvestLogHandler + { + private readonly IZone _zone; + private readonly ConcurrentQueue _HarvestLogs = new ConcurrentQueue(); + + public delegate HarvestLogHandler Factory(IZone zone); + + public HarvestLogHandler(IZone zone) + { + _zone = zone; + } + + public void EnqueueHarvestLog(int drilledMineralDefinition, int drilledQuantity) + { + var entry = new HarvestLogEntry + { + definition = drilledMineralDefinition, + quantity = drilledQuantity, + eventTime = DateTime.Today + }; + + _HarvestLogs.Enqueue(entry); + } + + private readonly IntervalTimer _timer = new IntervalTimer(TimeSpan.FromSeconds(10)); + + public void Update(TimeSpan time) + { + _timer.Update(time).IsPassed(() => Task.Run(() => WriteHarvestLogToSql())); + } + + private void WriteHarvestLogToSql() + { + if (_HarvestLogs.Count == 0) + return; + + Logger.Info("flushing Harvestlog. " + _HarvestLogs.Count + " long entries. "); + + var finalList = new List(); + + var workList = _HarvestLogs.TakeAll().ToList(); + + while (workList.Count > 0) + { + var firstItem = workList[0]; + workList.RemoveAt(0); + + if (workList.Count > 0) + { + firstItem.quantity += workList.Where(e => e.definition == firstItem.definition && e.eventTime == firstItem.eventTime).Sum(l => l.quantity); + workList.RemoveAll(e => e.definition == firstItem.definition && e.eventTime == firstItem.eventTime); + } + + finalList.Add(firstItem); + } + + var counter = 0; + if (finalList.Count <= 0) + return; + + foreach (var me in finalList) + { + Db.Query().CommandText("writeharvestlog") + .SetParameter("@zoneId", _zone.Id) + .SetParameter("@definition", me.definition) + .SetParameter("@quantity", me.quantity) + .SetParameter("@eventtime", me.eventTime) + .ExecuteNonQuery(); + + counter++; + } + + Logger.Info("Harvestlog fushed " + counter + " actual entries."); + } + + private struct HarvestLogEntry + { + public int definition; + public int quantity; + public DateTime eventTime; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/ProximityProbes/PBSRegisterHelper.cs b/src/Perpetuum/Zones/Helpers/PBSRegisterHelper.cs similarity index 100% rename from src/Perpetuum/Zones/ProximityProbes/PBSRegisterHelper.cs rename to src/Perpetuum/Zones/Helpers/PBSRegisterHelper.cs diff --git a/src/Perpetuum/Zones/IWeatherService.cs b/src/Perpetuum/Zones/IWeatherService.cs index 3e71afeb6..01cb6d907 100644 --- a/src/Perpetuum/Zones/IWeatherService.cs +++ b/src/Perpetuum/Zones/IWeatherService.cs @@ -7,5 +7,6 @@ public interface IWeatherService : IObservable,IProcess { [NotNull] WeatherInfo GetCurrentWeather(); + void SetCurrentWeather(WeatherInfo weather); } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/IZone.cs b/src/Perpetuum/Zones/IZone.cs index 1800844e7..4a40afd96 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -5,9 +5,13 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Log; using Perpetuum.Players; +using Perpetuum.Services.Relics; +using Perpetuum.Services.Strongholds; +using Perpetuum.Services.Weather; using Perpetuum.Units; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Decors; +using Perpetuum.Zones.Effects.ZoneEffects; using Perpetuum.Zones.Environments; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; @@ -22,6 +26,7 @@ namespace Perpetuum.Zones public interface IZone { [NotNull] ZoneConfiguration Configuration { get; } + bool IsLayerEditLocked { get; set; } int Id { get; } Size Size { get; } @@ -47,6 +52,9 @@ public interface IZone PBSHighwayHandler HighwayHandler { get; } TerraformHandler TerraformHandler { get; } MiningLogHandler MiningLogHandler { get; } + HarvestLogHandler HarvestLogHandler { get; } + IRelicManager RelicManager { get; } + IZoneEffectHandler ZoneEffectHandler { get; } IZoneUnitService UnitService { get; } IZoneEnterQueueService EnterQueueService { get; } @@ -57,6 +65,6 @@ public interface IZone void RemoveUnit(Unit unit); void SetGang(Player player); - void Enter(Character character,Command replyCommand); + void Enter(Character character, Command replyCommand); } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 412036472..807f185a0 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -15,6 +15,9 @@ using Perpetuum.Items.Templates; using Perpetuum.Log; using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Services.Looting; using Perpetuum.Timers; using Perpetuum.Units.DockingBases; @@ -25,13 +28,18 @@ namespace Perpetuum.Zones.Intrusion public class Outpost : DockingBase { private const int EP_WINNER = 120; - private const int EP_LOSER = 20; + private const int MAX_STABILITY = 150; + private const int MIN_STABILITY = 0; + private const int STARTING_STABILITY = 1; + private const int PRODUCTION_BONUS_THRESHOLD = 100; private TimeRange _intrusionWaitTime => IntrusionWaitTime; private readonly IEntityServices _entityServices; private readonly ICorporationManager _corporationManager; private readonly ILootService _lootService; private static readonly ILookup _sapInfos; + private readonly EventListenerService _eventChannel; + private readonly OutpostDecay _decay; public static StabilityBonusThreshold[] StabilityBonusThresholds { get; private set; } public static int DefenseNodesStabilityLimit { get; private set; } @@ -60,11 +68,24 @@ public Outpost(IEntityServices entityServices, ILootService lootService, ICentralBank centralBank, IRobotTemplateRelations robotTemplateRelations, + EventListenerService eventChannel, DockingBaseHelper dockingBaseHelper) : base(channelManager,centralBank,robotTemplateRelations,dockingBaseHelper) { _entityServices = entityServices; _corporationManager = corporationManager; _lootService = lootService; + _eventChannel = eventChannel; + _decay = new OutpostDecay(_eventChannel, this); + } + + private void InitStabilityListener() + { + _eventChannel.AttachListener(new AffectOutpostStability(this)); + } + + public void PublishSAPEvent(StabilityAffectingEvent eventMsg) + { + _eventChannel.PublishMessage(eventMsg); } public TimeRange IntrusionWaitTime { get; set; } @@ -137,6 +158,7 @@ public void AppendSiteInfoToPacket(Packet packet) protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); + _decay.OnUpdate(time); if (!Enabled || IntrusionInProgress) return; @@ -294,6 +316,7 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { base.OnEnterZone(zone, enterType); RefreshEffectBonus(); + InitStabilityListener(); } private bool _enabled = true; @@ -313,11 +336,10 @@ public bool Enabled private void OnSAPTakeOver(SAP sap) { + Task.Run(() => HandleTakeOver(sap)).ContinueWith(t => IntrusionInProgress = false); } - private const int STARTING_STABILITY = 5; - /// /// This function is called when players take over a sap successfully /// @@ -329,99 +351,169 @@ private void HandleTakeOver(SAP sap) { try { - var gen = new LootGenerator(_lootService.GetIntrusionLootInfos(this,sap)); + var gen = new LootGenerator(_lootService.GetIntrusionLootInfos(this, sap)); LootContainer.Create().AddLoot(gen).BuildAndAddToZone(Zone, sap.CurrentPosition); + ProcessStabilityChange(sap.ToStabilityAffectingEvent()); + scope.Complete(); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + } + } - var winnerCorporation = sap.GetWinnerCorporation(); - if ( winnerCorporation == null ) - return; + /// ////////////////// + /// + /// This function is called by external events that affect stability + /// + public void IntrusionEvent(StabilityAffectingEvent sap) + { + Logger.Info($"Intrusion Something else... taken."); + Logger.Info($"this outpost = {this.Eid} {this.ED.Name} "); - var siteInfo = GetIntrusionSiteInfo(); - var oldStability = siteInfo.Stability; - var newStability = siteInfo.Stability; - var newOwner = siteInfo.Owner; - var oldOwner = siteInfo.Owner; + using (var scope = Db.CreateTransaction()) + { + try + { + ProcessStabilityChange(sap); + scope.Complete(); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + } + } + // - var logEvent = new IntrusionLogEvent - { - OldOwner = siteInfo.Owner, - NewOwner = siteInfo.Owner, - SapDefinition = sap.Definition, - EventType = IntrusionEvents.sapTakeOver, - WinnerCorporationEid = winnerCorporation.Eid, - OldStability = oldStability - }; - - if (winnerCorporation is PrivateCorporation) - { - if (winnerCorporation.Eid == siteInfo.Owner) - { - newStability = (newStability + sap.StabilityChange).Clamp(0, 100); - } - else - { - newStability = (newStability - sap.StabilityChange).Clamp(0, 100); - - // csak akkor ha 0 lett a stability - if (newStability == 0) - { - if (siteInfo.Owner != null) - { - // ha van owner akkor eloszor toroljuk a regit es majd a kovetkezo korben kap ujat - logEvent.EventType = IntrusionEvents.siteOwnershipLost; - newOwner = null; - } - else - { - // itt kap uj ownert es egy kezdo stability erteket - logEvent.EventType = IntrusionEvents.siteOwnershipGain; - newOwner = winnerCorporation.Eid; - newStability = STARTING_STABILITY; - } - } - } + /// ////////////////// + /// + /// Core SAP logic + /// + private void ProcessStabilityChange(StabilityAffectingEvent sap) + { + // Check for invalid player-SAPS + var winnerCorporation = sap.GetWinnerCorporation(); + if (winnerCorporation == null && !sap.IsSystemGenerated()) + return; - //set the resulting values - SetIntrusionOwnerAndPoints(newOwner, newStability); - ReactStabilityChanges(siteInfo, oldStability, newStability, newOwner, oldOwner); - } + // Check for unowned outposts to not be affected by system-generated events + var siteInfo = GetIntrusionSiteInfo(); + if (siteInfo.Owner == null && sap.IsSystemGenerated()) + return; - logEvent.NewOwner = newOwner; - logEvent.NewStability = newStability; - InsertIntrusionLog(logEvent); + var oldStability = siteInfo.Stability; + var newStability = siteInfo.Stability; + var newOwner = siteInfo.Owner; + var oldOwner = siteInfo.Owner; - foreach (var playerInfo in sap.GetPlayersWithScore()) - { - var ep = playerInfo.corporationEid == winnerCorporation.Eid ? EP_WINNER : EP_LOSER; - var character = playerInfo.character; - character.AddExtensionPointsBoostAndLog( EpForActivityType.Intrusion, ep); - } + // Reset Decay timer on any StabilityAffectingEvent completed by the owner + if (winnerCorporation.Eid == siteInfo.Owner) + { + _decay.ResetDecayTimer(); + } - //make dem toast anyways - Transaction.Current.OnCommited(() => - { - if (oldStability != newStability) - { - OnIntrusionSiteInfoUpdated(); - InformAllPlayers(); - } + var logEvent = new IntrusionLogEvent + { + OldOwner = siteInfo.Owner, + NewOwner = siteInfo.Owner, + SapDefinition = sap.Definition, + EventType = IntrusionEvents.sapTakeOver, + WinnerCorporationEid = winnerCorporation.Eid, + OldStability = oldStability + }; + + if (sap.IsSystemGenerated() || sap.OverrideRelations) + { + newStability = (newStability + sap.StabilityChange); + } + else if (winnerCorporation is PrivateCorporation) + { + //Compare the Owner and Winner corp's relations + var ownerEid = siteInfo.Owner ?? default(long); + var ownerAndWinnerGoodRelation = false; + + //Ally relationship threshold + var friendlyOnly = 10; + //Ally stability affect + var allyAffectFactor = 0.0; + //Compare mutual relation match between corps to determine ally + ownerAndWinnerGoodRelation = _corporationManager.IsStandingMatch(winnerCorporation.Eid, ownerEid, friendlyOnly); + ownerAndWinnerGoodRelation = _corporationManager.IsStandingMatch(ownerEid, winnerCorporation.Eid, friendlyOnly) && ownerAndWinnerGoodRelation; + + //Stability increase if winner is owner, 0 increase if ally, else negative + if (winnerCorporation.Eid == siteInfo.Owner) + { + newStability = (newStability + sap.StabilityChange); + } + else if (ownerAndWinnerGoodRelation) + { + newStability = (newStability + (int)(sap.StabilityChange * allyAffectFactor)); + } + else + { + newStability = (newStability - sap.StabilityChange); + } + } - InformPlayersOnZone(Commands.ZoneSapActivityEnd, new Dictionary + if (siteInfo.Owner == null) + { + if (winnerCorporation is PrivateCorporation) + { + // No owner - winner gets outpost, for any SAP event + logEvent.EventType = IntrusionEvents.siteOwnershipGain; + newOwner = winnerCorporation.Eid; + newStability = STARTING_STABILITY; + _decay.ResetDecayTimer(); + } + else + { + // No owner - winner is non-player corp => stability stays at 0 + newStability = MIN_STABILITY; + } + } + else if (newStability <= 0) + { + // Outpost has owner, but new stability hit 0 = owner loses station + logEvent.EventType = IntrusionEvents.siteOwnershipLost; + newOwner = null; + } + + newStability = newStability.Clamp(MIN_STABILITY, MAX_STABILITY); + + //set the resulting values + SetIntrusionOwnerAndPoints(newOwner, newStability); + ReactStabilityChanges(siteInfo, oldStability, newStability, newOwner, oldOwner); + logEvent.NewOwner = newOwner; + logEvent.NewStability = newStability; + InsertIntrusionLog(logEvent); + + //Award EP + foreach (var player in sap.Participants) + { + player.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Intrusion, EP_WINNER); + } + + //make dem toast anyways + Transaction.Current.OnCommited(() => + { + if (oldStability != newStability) + { + OnIntrusionSiteInfoUpdated(); + InformAllPlayers(); + } + if (!sap.IsSystemGenerated()) + { + InformPlayersOnZone(Commands.ZoneSapActivityEnd, new Dictionary { {k.siteEID, Eid}, {k.eventType, (int) logEvent.EventType}, {k.eid, sap.Eid}, {k.winner, winnerCorporation.Eid}, }); - }); - - scope.Complete(); } - catch (Exception ex) - { - Logger.Exception(ex); - } - } + }); } @@ -434,16 +526,16 @@ private void OnSAPTimeOut(SAP sap) private readonly Dictionary _refundMultipliers = new Dictionary { - {TransactionType.hangarRent, 0.5}, - {TransactionType.hangarRentAuto, 0.5}, - {TransactionType.marketFee, 1.0}, - {TransactionType.MarketTax, 1.0}, - {TransactionType.ProductionManufacture, 0.5}, - {TransactionType.ProductionResearch, 0.5}, - {TransactionType.ProductionMultiItemRepair, 0.75}, - {TransactionType.ItemRepair, 0.75}, - {TransactionType.ProductionPrototype, 0.5}, - {TransactionType.ProductionMassProduction, 0.5}, + {TransactionType.hangarRent, 0.25}, + {TransactionType.hangarRentAuto, 0.25}, + {TransactionType.marketFee, 0.5}, + {TransactionType.MarketTax, 0.5}, + {TransactionType.ProductionManufacture, 0.25}, + {TransactionType.ProductionResearch, 0.25}, + {TransactionType.ProductionMultiItemRepair, 0.35}, + {TransactionType.ItemRepair, 0.35}, + {TransactionType.ProductionPrototype, 0.25}, + {TransactionType.ProductionMassProduction, 0.25}, }; public override double GetOwnerRefundMultiplier(TransactionType transactionType) @@ -518,6 +610,9 @@ private void ReactStabilityChanges(IntrusionSiteInfo siteInfo, int oldStability, /// private void ProductionStabilityGain(int newStability, int oldStability, long? newOwner) { + if (oldStability > PRODUCTION_BONUS_THRESHOLD) + return; //Do nothing if old stability > 100 + var siteInfo = GetIntrusionSiteInfo(); var oldProductionPoints = (int) (oldStability/10.0); var newProductionPoints = (int) (newStability/10.0); @@ -547,6 +642,9 @@ private void ProductionStabilityGain(int newStability, int oldStability, long? n /// private void ProductionStabilityLoss(int newStability, int oldStability, long? newOwner, long? oldOwner) { + if (newStability > PRODUCTION_BONUS_THRESHOLD) + return; //Do nothing if new stability > 100 + var siteInfo = GetIntrusionSiteInfo(); var oldProductionPoints = (int)(oldStability / 10.0); var newProductionPoints = (int)(newStability / 10.0); diff --git a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs new file mode 100644 index 000000000..7382052b8 --- /dev/null +++ b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs @@ -0,0 +1,58 @@ +using System; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.EntityFramework; + +namespace Perpetuum.Zones.Intrusion +{ + /// + /// Outpost Decay handler - updates outpost stability with a system-generated SAP event + /// + public class OutpostDecay + { + private readonly EventListenerService _eventChannel; + private readonly static TimeSpan noDecayBefore = TimeSpan.FromDays(3); + private readonly static TimeSpan decayRate = TimeSpan.FromDays(1); + private TimeSpan timeSinceLastDecay = TimeSpan.Zero; + private TimeSpan lastSuccessfulIntrusion = TimeSpan.Zero; + private readonly static int decayPts = -5; + private StabilityAffectingEvent.StabilityAffectBuilder _builder; + + public OutpostDecay(EventListenerService eventChannel, Outpost outpost) + { + _eventChannel = eventChannel; + var def = EntityDefault.GetByName("def_outpost_decay"); + _builder = StabilityAffectingEvent.Builder() + .WithOutpost(outpost) + .WithSapDefinition(def.Definition) + .WithPoints(decayPts); + } + + public void OnUpdate(TimeSpan time) + { + lastSuccessfulIntrusion += time; + if (lastSuccessfulIntrusion < noDecayBefore) + return; + + timeSinceLastDecay += time; + if (timeSinceLastDecay > decayRate) + { + timeSinceLastDecay = TimeSpan.Zero; + DoDecay(); + } + } + + /// + /// Called when a SAP is completed + /// + public void ResetDecayTimer() + { + lastSuccessfulIntrusion = TimeSpan.Zero; + } + + private void DoDecay() + { + _eventChannel.PublishMessage(_builder.Build()); + } + } +} diff --git a/src/Perpetuum/Zones/Intrusion/SAP.cs b/src/Perpetuum/Zones/Intrusion/SAP.cs index 191b3d905..082e972ee 100644 --- a/src/Perpetuum/Zones/Intrusion/SAP.cs +++ b/src/Perpetuum/Zones/Intrusion/SAP.cs @@ -9,6 +9,8 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Log; using Perpetuum.Players; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Threading; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Beams; @@ -50,17 +52,19 @@ public IntrusionCorporationScore(long corporationEid, int score) } } - private static readonly TimeSpan _sapExpiry = + private static readonly TimeSpan _sapExpiry = #if (DEBUG) TimeSpan.FromMinutes(5); #else TimeSpan.FromHours(1); #endif + private static readonly TimeSpan _timerExtension = TimeSpan.FromMinutes(15); + + private static readonly TimeSpan _enterBeamDuration = TimeSpan.FromSeconds(3); + private static readonly TimeSpan _exitBeamDuration = TimeSpan.FromSeconds(3); - private readonly TimeSpan _enterBeamDuration = TimeSpan.FromSeconds(3); - private readonly TimeSpan _exitBeamDuration = TimeSpan.FromSeconds(3); - private static readonly TimeSpan _broadcastInfoInterval = TimeSpan.FromSeconds(2); + private const int BROADCAST_INFO_TOP_SCORE_COUNT = 10; private const int BROADCAST_INFO_RANGE = 100; @@ -70,13 +74,16 @@ public IntrusionCorporationScore(long corporationEid, int score) private readonly IntervalTimer _broadcastInfoTimer = new IntervalTimer(_broadcastInfoInterval); private readonly TimeTracker _expiryTimer = new TimeTracker(_sapExpiry); + private static readonly TimeSpan _threadTimeout = TimeSpan.FromSeconds(1); + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + public event Action TimeOut; public event Action TakeOver; protected SAP(BeamType enterBeamType, BeamType exitBeamType) { _enterBeamType = enterBeamType; - _exitBeamType = exitBeamType; + _exitBeamType = exitBeamType; } public void AddToZone(IZone zone,Position spawnPosition) @@ -93,16 +100,19 @@ protected override void OnUpdate(TimeSpan time) _broadcastInfoTimer.Update(time); - if ( _broadcastInfoTimer.Passed ) + if (_broadcastInfoTimer.Passed) { _broadcastInfoTimer.Reset(); BroadcastSAPInfoPacket(); } - _expiryTimer.Update(time); + using (_lock.Write(_threadTimeout)) + { + _expiryTimer.Update(time); - if (!_expiryTimer.Expired) - return; + if (!_expiryTimer.Expired) + return; + } // lejart OnRemove(); @@ -118,8 +128,7 @@ private void OnRemove() private void SendSAPPlayerInfoPacketToPlayer(Character character,int score) { - Player player; - if (Zone.TryGetPlayer(character, out player)) + if (Zone.TryGetPlayer(character, out Player player)) { SendSAPPlayerInfoPacketToPlayer(player, score); } @@ -166,7 +175,6 @@ protected void RemovePlayerInfo(Character character) } } - protected void IncrementPlayerScore(Player player, int score) { player.ApplyPvPEffect(); @@ -181,9 +189,24 @@ protected void IncrementPlayerScore(Player player, int score) OnTakeOver(); } + ExtendTimerOnce(); + Logger.Info($"Intrusion SAP score updated. sap = {Eid} ({ED.Name}) player = {info.character.Id} score = {info.score}"); } + private bool _firstSapAttempt = true; + private void ExtendTimerOnce() + { + if (_firstSapAttempt) + { + using (_lock.Write(_threadTimeout)) + { + _expiryTimer.Extend(_timerExtension); + } + _firstSapAttempt = false; + } + } + public Outpost Site { get; set; } private int _takeOver; @@ -236,11 +259,14 @@ private Packet BuildSAPInfoPacket() Site.AppendSiteInfoToPacket(packet); - packet.AppendInt((int) _expiryTimer.Duration.TotalMilliseconds); - packet.AppendInt((int) _expiryTimer.Remaining.TotalMilliseconds); + using (_lock.Read(_threadTimeout)) + { + packet.AppendInt((int)_expiryTimer.Duration.TotalMilliseconds); + packet.AppendInt((int)_expiryTimer.Remaining.TotalMilliseconds); + } packet.AppendInt(MaxScore); // max score - AppendTopScoresToPacket(packet,BROADCAST_INFO_TOP_SCORE_COUNT); + AppendTopScoresToPacket(packet, BROADCAST_INFO_TOP_SCORE_COUNT); return packet; } @@ -321,5 +347,26 @@ private IEnumerable GetCorporationScores() return result; } + + /// + /// Adapter method for SAP->StabilityAffectingEvent + /// + /// StabilityAffectingEvent + public StabilityAffectingEvent ToStabilityAffectingEvent() + { + List players = new List(); + foreach(var player in this.PlayerInfos) + { + players.Add(player.character.GetPlayerRobotFromZone()); + } + var builder = StabilityAffectingEvent.Builder() + .WithOutpost(Site) + .WithSapDefinition(Definition) + .WithSapEntityID(Eid) + .WithPoints(StabilityChange) + .AddParticipants(players) + .WithWinnerCorp(GetWinnerCorporationEid()); + return builder.Build(); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Intrusion/SpecimenProcessingSAP.cs b/src/Perpetuum/Zones/Intrusion/SpecimenProcessingSAP.cs index 547a7a47a..349a5d834 100644 --- a/src/Perpetuum/Zones/Intrusion/SpecimenProcessingSAP.cs +++ b/src/Perpetuum/Zones/Intrusion/SpecimenProcessingSAP.cs @@ -34,9 +34,9 @@ public SiegeItem(int definition, IntRange quantity) private const int SUBMIT_ITEM_RANGE = 7; - private static readonly TimeSpan _submitItemCooldown = TimeSpan.FromMinutes(1); + private static readonly TimeSpan _submitItemCooldown = TimeSpan.FromMinutes(1.5); - private static readonly IntRange _requiredItems = new IntRange(3, 4); + private static readonly IntRange _requiredItems = new IntRange(5, 6); private readonly IList _itemInfos; private readonly ConcurrentDictionary _playerItemProgresses = new ConcurrentDictionary(); @@ -64,21 +64,16 @@ public SpecimenProcessingSAP() : base(BeamType.attackpoint_item_enter, BeamType. /// private static IList GenerateSpecimenProcessingItemList(int count = 5) { - var result = new Dictionary(); - - while (count > 0) + var result = new List(); + while(result.Count < count) { - var randomItemInfo = _specimenProcessingItems.Where(d => !result.ContainsKey(d.Key)).RandomElement(); - + var randomItemInfo = _specimenProcessingItems.RandomElement(); var siegeItem = randomItemInfo.Value; var randomQty = FastRandom.NextInt(siegeItem.quantity); - var itemInfo = new ItemInfo(siegeItem.definition, randomQty); - result.Add(randomItemInfo.Key, itemInfo); - count--; + result.Add(itemInfo); } - - return result.Values.ToArray(); + return result; } diff --git a/src/Perpetuum/Zones/LandMines/LandMine.cs b/src/Perpetuum/Zones/LandMines/LandMine.cs new file mode 100644 index 000000000..dbaacada1 --- /dev/null +++ b/src/Perpetuum/Zones/LandMines/LandMine.cs @@ -0,0 +1,96 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Modules.Weapons; +using Perpetuum.Players; +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.ProximityProbes; +using Perpetuum.Zones.Teleporting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.LandMines +{ + public class LandMine : ProximityDeviceBase + { + private const int BeamDistance = 600; + private readonly IntervalTimer gracePeriodInterval = new IntervalTimer(TimeSpan.FromSeconds(15)); + + public int TriggerMass => ED.Options.GetOption("triggerMass"); + + protected override void OnUpdate(TimeSpan time) + { + if (!gracePeriodInterval.Passed) + { + _ = gracePeriodInterval.Update(time); + + return; + } + + base.OnUpdate(time); + } + + public override void OnUnitsFound(List unitsFound) + { + if (unitsFound.Exists(x => x.ActualMass > TriggerMass)) + { + unitsFound[0].Zone.CreateBeam( + BeamType.plant_bomb_explosion,//.timebomb_explosion, + builder => builder + .WithPosition(CurrentPosition) + .WithState(BeamState.Hit) + .WithVisibility(BeamDistance)); + + // There be explosion + IDamageBuilder damageBuilder = DamageInfo.Builder.WithAttacker(this) + .WithDamage(DamageType.Chemical, ED.Config.damage_chemical ?? 5000.0) + .WithDamage(DamageType.Explosive, ED.Config.damage_explosive ?? 5000.0) + .WithDamage(DamageType.Kinetic, ED.Config.damage_kinetic ?? 5000.0) + .WithDamage(DamageType.Thermal, ED.Config.damage_thermal ?? 5000.0) + .WithDamage(DamageType.Toxic, ED.Config.damage_toxic ?? 5000.0) + .WithOptimalRange(2) + .WithFalloff(ED.Config.item_work_range ?? 30.0) + .WithExplosionRadius(ED.Config.explosion_radius ?? 50.0); + + unitsFound[0].Zone.DoAoeDamageAsync(damageBuilder); + + OnDead(unitsFound[0]); + } + } + + #region probe functions + + [CanBeNull] + public override List GetNoticedUnits() + { + return GetVisibleUnits().Select(v => v.Target).OfType().ToList(); + } + + protected override bool IsDetected(Unit target) + { + double range = ED.Config.item_work_range ?? 5.0; + + return IsInRangeOf3D(target, range); + } + + public override void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) + { + zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_BASE).Any().ThrowIfTrue(ErrorCodes.NotDeployableNearObject); + zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_TELEPORT).Any().ThrowIfTrue(ErrorCodes.TeleportIsInRange); + zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_LANDMINE).Any().ThrowIfTrue(ErrorCodes.TooCloseToOtherDevice); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + #endregion + } +} diff --git a/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs b/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs new file mode 100644 index 000000000..b52c3e4a3 --- /dev/null +++ b/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs @@ -0,0 +1,82 @@ +using Perpetuum.Deployers; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Groups.Corporations; +using Perpetuum.Players; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.Teleporting; +using System; +using System.Linq; + +namespace Perpetuum.Zones.LandMines +{ + public class LandMineDeployer : ItemDeployer + { + private readonly IEntityServices _entityServices; + + public LandMineDeployer(IEntityServices entityServices) : base(entityServices) + { + _entityServices = entityServices; + } + + protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, Player player) + { + zone.Configuration.Protected.ThrowIfTrue(ErrorCodes.OnlyUnProtectedZonesAllowed); + + var corporation = player.Character.GetPrivateCorporationOrThrow(); + + var maxLandMines = 100; // corporation.GetMaximumProbeAmount(); + corporation.GetLandMineEids().Count().ThrowIfGreaterOrEqual(maxLandMines, ErrorCodes.MaximumAmountOfProbesReached); + + var landMine = (LandMine)_entityServices.Factory.CreateWithRandomEID(DeployableItemEntityDefault); + landMine.CheckDeploymentAndThrow(zone, spawnPosition); //Enforce min-distance separations + landMine.Owner = corporation.Eid; + var zoneStorage = zone.Configuration.GetStorage(); + landMine.Parent = zoneStorage.Eid; + landMine.Save(); + + zone.UnitService.AddUserUnit(landMine, spawnPosition); + + var initialMembers = corporation.GetMembersWithAnyRoles(CorporationRole.CEO, CorporationRole.DeputyCEO).Select(cm => cm.character).ToList(); + initialMembers.Add(player.Character); + + landMine.Init(initialMembers.Distinct()); + landMine.SetDespawnTime(this.ProximityProbeDespawnTime); + + return landMine; + } + + protected override ErrorCodes CanDeploy(IZone zone, Unit unit, Position spawnPosition, Player player) + { + if (zone.Configuration.Protected) + return ErrorCodes.OnlyUnProtectedZonesAllowed; + + if (!zone.Configuration.Terraformable) + { + if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_BASE).Any()) + return ErrorCodes.NotDeployableNearObject; + + if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_TELEPORT).Any()) + return ErrorCodes.NotDeployableNearObject; + } + else + { + if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_LANDMINE).Any()) + return ErrorCodes.TooCloseToOtherDevice; + } + + return base.CanDeploy(zone, unit, spawnPosition, player); + } + + private TimeSpan ProximityProbeDespawnTime + { + get + { + var m = GetPropertyModifier(AggregateField.despawn_time); + return TimeSpan.FromMilliseconds((int)m.Value); + } + } + } +} diff --git a/src/Perpetuum/Zones/LineOfSight.cs b/src/Perpetuum/Zones/LineOfSight.cs index b5c0467e2..d0131bc2d 100644 --- a/src/Perpetuum/Zones/LineOfSight.cs +++ b/src/Perpetuum/Zones/LineOfSight.cs @@ -55,13 +55,14 @@ public static LOSResult IsInLineOfSight(this IZone zone, Unit sourceUnit, Unit t if (targetUnit.HitSize > 0) { - len = (float)CylinderIntersection(source,direction,targetUnit.CurrentPosition.ToVector3(),target,targetUnit.HitSize); + var hitsize = (targetUnit.HitSize * 0.5).Clamp(1, 10); + len = (float)CylinderIntersection(source, direction, targetUnit.CurrentPosition.ToVector3(), target, hitsize); } - return IsInLineOfSight(zone,source,direction,len,ballistic); + return IsInLineOfSight(zone, source, direction, len, ballistic); } - private static double CylinderIntersection(Vector3 start,Vector3 dir,Vector3 cylinderStart,Vector3 cylinderEnd,double radius) + private static double CylinderIntersection(Vector3 start, Vector3 dir, Vector3 cylinderStart, Vector3 cylinderEnd, double radius) { var ab = cylinderEnd - cylinderStart; var ao = start - cylinderStart; @@ -97,8 +98,20 @@ public static LOSResult IsInLineOfSight(this IZone zone, Position start, Unit ta } [NotNull] - private static LOSResult IsInLineOfSight(IZone zone,Vector3 origin,Vector3 direction,float distance,bool ballistic) + private static LOSResult IsInLineOfSight(IZone zone, Vector3 origin, Vector3 direction, float distance, bool ballistic) { + if (distance.IsApproximatelyEqual(0.0f)) + { + var blockingInfo = zone.Terrain.Blocks.GetValue(origin); + var losResult = new LOSResult + { + hit = true, + position = (Position)origin, + blockingFlags = blockingInfo.Flags + }; + return losResult; + } + var lastAltitude = zone.Terrain.Altitude.GetAltitudeAsDouble(origin) + 2; var lx = (int) origin.X; diff --git a/src/Perpetuum/Zones/Locking/LockHandler.cs b/src/Perpetuum/Zones/Locking/LockHandler.cs index cebbfb070..c73fe2d71 100644 --- a/src/Perpetuum/Zones/Locking/LockHandler.cs +++ b/src/Perpetuum/Zones/Locking/LockHandler.cs @@ -10,6 +10,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Blobs; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking.UnitProperties; namespace Perpetuum.Zones.Locking { @@ -19,32 +20,20 @@ public class LockHandler private readonly ItemProperty _lockingTime; private readonly ItemProperty _maxTargetingRange; private readonly ItemProperty _maxLockedTargets; - private List _locks = new List(); private readonly ConcurrentQueue _newLocks = new ConcurrentQueue(); private readonly ConcurrentQueue _removedLocks = new ConcurrentQueue(); + private List _locks = new List(); private int _dirty; - public LockHandler(Robot owner) - { - _owner = owner; - _lockingTime = new LockingTimeProperty(owner); - owner.AddProperty(_lockingTime); + public double MaxTargetingRange { get { return _maxTargetingRange.Value; } } - _maxTargetingRange = new MaxTargetingRangeProperty(owner); - owner.AddProperty(_maxTargetingRange); + private int MaxLockedTargets { get { return (int)_maxLockedTargets.Value; } } - _maxLockedTargets = new MaxLockedTargetsProperty(owner); - owner.AddProperty(_maxLockedTargets); + public int Count { get { return _locks.Count; } } - if (owner is IBlobableUnit) - { - owner.PropertyChanged += OnOwnerPropertyChanged; - } - } + public event LockEventHandler LockStateChanged; - public double MaxTargetingRange { get { return _maxTargetingRange.Value; } } - private int MaxLockedTargets { get { return (int) _maxLockedTargets.Value; } } - public int Count { get { return _locks.Count; } } + public event LockEventHandler LockError; public List Locks { @@ -56,67 +45,55 @@ public bool HasFreeLockSlot get { return Count < MaxLockedTargets; } } - private void OnOwnerPropertyChanged(Item unit, ItemProperty property) + public LockHandler(Robot owner) { - if (property.Field != AggregateField.blob_effect) - return; + _owner = owner; + _lockingTime = new LockingTimeProperty(owner); + owner.AddProperty(_lockingTime); + _maxTargetingRange = new MaxTargetingRangeProperty(owner); + owner.AddProperty(_maxTargetingRange); + _maxLockedTargets = new MaxLockedTargetsProperty(owner); + owner.AddProperty(_maxLockedTargets); - _lockingTime.Update(); - _maxTargetingRange.Update(); + if (owner is IBlobableUnit) + { + owner.PropertyChanged += OnOwnerPropertyChanged; + } } public void AddLock(long targetEid, bool isPrimary) { var targetUnit = _owner.Zone?.GetUnit(targetEid); + if (targetUnit == null) + { return; + } AddLock(targetUnit,isPrimary); } public void AddLock(Unit target, bool isPrimary) { - AddLock(new UnitLock(_owner) { Target = target, Primary = isPrimary }); - } - - public void AddLock(Lock newLock) - { - _newLocks.Enqueue(newLock); - Interlocked.Exchange(ref _dirty, 1); - } - - private void RemoveLock(Lock @lock) - { - _removedLocks.Enqueue(@lock); - Interlocked.Exchange(ref _dirty, 1); - } - - private void ProcessRemovedLocks(IList locks) - { - Lock removedLock; - while (_removedLocks.TryDequeue(out removedLock)) + AddLock(new UnitLock(_owner) { - locks.Remove(removedLock); - } + Target = target, + Primary = isPrimary, + }); } - public event LockEventHandler LockStateChanged; - - private void OnLockStateChanged(Lock @lock) + public void AddLock(Position position, bool isPrimary) { - if (@lock.State == LockState.Disabled) + AddLock(new TerrainLock(_owner, position) { - RemoveLock(@lock); - } - - LockStateChanged?.Invoke(@lock); + Primary = isPrimary, + }); } - public event LockEventHandler LockError; - - private void OnLockError(Lock @lock, ErrorCodes error) + public void AddLock(Lock newLock) { - LockError?.Invoke(@lock, error); + _newLocks.Enqueue(newLock); + Interlocked.Exchange(ref _dirty, 1); } public void Update(TimeSpan time) @@ -140,75 +117,30 @@ public void Update(TimeSpan time) @lock.Update(time); if (!ValidateLock(@lock)) - @lock.Cancel(); - } - } - - private void ProcessNewLocks(IList locks) - { - Lock newLock; - while (_newLocks.TryDequeue(out newLock)) - { - if (locks.Any(l => l.Equals(newLock))) - continue; - - if (locks.Count >= MaxLockedTargets) - { - OnLockError(newLock,ErrorCodes.MaxLockedTargetExceed); - continue; - } - - if (!ValidateLock(newLock)) - continue; - - if (newLock.Primary) { - var currentPrimaryLock = locks.FirstOrDefault(l => l.Primary); - if (currentPrimaryLock != null) - currentPrimaryLock.Primary = false; + @lock.Cancel(); } - - - - locks.Add(newLock); - - newLock.Changed += OnLockStateChanged; - - var lockingTime = TimeSpan.Zero; - - var terrainLock = newLock as TerrainLock; - if (terrainLock == null) - lockingTime = TimeSpan.FromMilliseconds(_lockingTime.Value); - - newLock.Start(lockingTime); } } - private bool ValidateLock(Lock newLock) - { - var validator = new LockValidator(this); - newLock.AcceptVisitor(validator); - - if (validator.Error == ErrorCodes.NoError) - return true; - - OnLockError(newLock, validator.Error); - return false; - } - public void SetPrimaryLock(Lock primaryLock) { var currentPrimaryLock = GetPrimaryLock(); + if (currentPrimaryLock != null) { if (currentPrimaryLock == primaryLock) + { return; + } currentPrimaryLock.Primary = false; } if (primaryLock != null) + { primaryLock.Primary = true; + } } public void SetPrimaryLock(long lockId) @@ -221,7 +153,9 @@ public void SetPrimaryLock(long lockId) public Lock GetLock(long lockId) { if (lockId == 0) + { return null; + } return _locks.FirstOrDefault(l => l.Id == lockId); } @@ -229,6 +163,7 @@ public Lock GetLock(long lockId) public void CancelLock(long lockId) { var l = GetLock(lockId); + l?.Cancel(); } @@ -246,6 +181,11 @@ public Lock GetPrimaryLock() return _locks.FirstOrDefault(l => l.Primary); } + public IEnumerable GetSecondaryLocks() + { + return _locks.Where(l => !l.Primary); + } + public bool IsLocked(Unit unit) { return _locks.OfType().Any(l => unit.Eid == l.Target.Eid); @@ -258,9 +198,9 @@ public UnitLock GetLockByUnit(Unit unit) } [CanBeNull] - private UnitLock GetLockByEid(long unitEid) + public TerrainLock GetLockByPosition(Position position) { - return _locks.OfType().FirstOrDefault(l => l.Target.Eid == unitEid); + return GetLockByPositionString(position.ToString()); } public bool IsInLockingRange(Unit target) @@ -273,66 +213,122 @@ public bool IsInLockingRange(Position targetPosition) return _owner.CurrentPosition.IsInRangeOf3D(targetPosition, MaxTargetingRange); } - private class LockingTimeProperty : UnitProperty + private void OnOwnerPropertyChanged(Item unit, ItemProperty property) { - public LockingTimeProperty(Unit owner) - : base(owner, AggregateField.locking_time, - AggregateField.locking_time_modifier, - // effektek - AggregateField.effect_sensor_booster_locking_time_modifier, - AggregateField.effect_sensor_dampener_locking_time_modifier, - AggregateField.effect_locking_time_modifier) { } - - protected override double CalculateValue() + if (property.Field != AggregateField.blob_effect) { - var v = base.CalculateValue(); + return; + } - var blobableUnit = owner as IBlobableUnit; - blobableUnit?.BlobHandler.ApplyBlobPenalty(ref v, -5); + _lockingTime.Update(); + _maxTargetingRange.Update(); + } - return v; - } + private void RemoveLock(Lock @lock) + { + _removedLocks.Enqueue(@lock); + Interlocked.Exchange(ref _dirty, 1); } - private class MaxLockedTargetsProperty : UnitProperty + private void ProcessRemovedLocks(IList locks) { - public MaxLockedTargetsProperty(Unit owner) : base(owner, AggregateField.locked_targets_max) + Lock removedLock; + + while (_removedLocks.TryDequeue(out removedLock)) { + locks.Remove(removedLock); } + } - protected override double CalculateValue() + private void OnLockError(Lock @lock, ErrorCodes error) + { + LockError?.Invoke(@lock, error); + } + + private void OnLockStateChanged(Lock @lock) + { + if (@lock.State == LockState.Disabled) { - var v = base.CalculateValue(); + RemoveLock(@lock); + } + + LockStateChanged?.Invoke(@lock); + } - if (owner.GetCharacter() == Character.None) - return v; + private bool ValidateLock(Lock newLock) + { + var validator = new LockValidator(this); - var lockedTargetsMaxBonus = owner.GetPropertyModifier(AggregateField.locked_targets_max_bonus); - v = Math.Min(lockedTargetsMaxBonus.Value + 1, v); + newLock.AcceptVisitor(validator); - return v; + if (validator.Error == ErrorCodes.NoError) + { + return true; } + + OnLockError(newLock, validator.Error); + + return false; } - private class MaxTargetingRangeProperty : UnitProperty + private void ProcessNewLocks(IList locks) { - public MaxTargetingRangeProperty(Unit owner) - : base(owner, AggregateField.locking_range, - AggregateField.locking_range_modifier, - //effektek - AggregateField.effect_sensor_booster_locking_range_modifier, - AggregateField.effect_sensor_dampener_locking_range_modifier, - AggregateField.effect_locking_range_modifier) { } - - protected override double CalculateValue() + Lock newLock; + + while (_newLocks.TryDequeue(out newLock)) { - var v = base.CalculateValue(); + if (locks.Any(l => l.Equals(newLock))) + { + continue; + } + + // if you are a tooladmin then lock up anything you want. + // this is easier than making special bots to work with terrain. + if (locks.Count >= MaxLockedTargets && _owner.GetCharacter().AccessLevel != AccessLevel.toolAdmin) + { + OnLockError(newLock, ErrorCodes.MaxLockedTargetExceed); + + continue; + } + + if (!ValidateLock(newLock)) + continue; + + if (newLock.Primary) + { + var currentPrimaryLock = locks.FirstOrDefault(l => l.Primary); + + if (currentPrimaryLock != null) + { + currentPrimaryLock.Primary = false; + } + } - var blobableUnit = owner as IBlobableUnit; - blobableUnit?.BlobHandler.ApplyBlobPenalty(ref v, 0.5); + locks.Add(newLock); + newLock.Changed += OnLockStateChanged; + + var lockingTime = TimeSpan.Zero; + var terrainLock = newLock as TerrainLock; + + if (terrainLock == null) + { + lockingTime = TimeSpan.FromMilliseconds(_lockingTime.Value); + } - return v; + newLock.Start(lockingTime); } } + + [CanBeNull] + private UnitLock GetLockByEid(long unitEid) + { + return _locks.OfType().FirstOrDefault(l => l.Target.Eid == unitEid); + } + + [CanBeNull] + private TerrainLock GetLockByPositionString(string positionString) + { + return _locks.OfType().FirstOrDefault(l => l.Location.ToString() == positionString); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Locking/Locks/TerraformDirection.cs b/src/Perpetuum/Zones/Locking/Locks/TerraformDirection.cs new file mode 100644 index 000000000..f43fb946d --- /dev/null +++ b/src/Perpetuum/Zones/Locking/Locks/TerraformDirection.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.Locking.Locks +{ + public enum TerraformDirection + { + Undefined, + Lower, + Raise, + } +} diff --git a/src/Perpetuum/Zones/Locking/Locks/TerrainLock.cs b/src/Perpetuum/Zones/Locking/Locks/TerrainLock.cs index a34cf27f7..2cf3d723e 100644 --- a/src/Perpetuum/Zones/Locking/Locks/TerrainLock.cs +++ b/src/Perpetuum/Zones/Locking/Locks/TerrainLock.cs @@ -1,52 +1,13 @@ -using Perpetuum.Builders; using Perpetuum.Robots; using Perpetuum.Zones.Terrains.Terraforming; namespace Perpetuum.Zones.Locking.Locks { - public enum TerraformDirection - { - Undefined, - Lower, - Raise - } - - public class TerrainLockParametersPacketBuilder : IBuilder - { - private readonly TerrainLock _terrainLock; - - public TerrainLockParametersPacketBuilder(TerrainLock terrainLock) - { - _terrainLock = terrainLock; - } - - public Packet Build() - { - var packet = new Packet(ZoneCommand.TerrainLockParameters); - - packet.AppendLong(_terrainLock.Id); - packet.AppendByte((byte) _terrainLock.TerraformType); - packet.AppendByte((byte) _terrainLock.TerraformDirection); - packet.AppendByte((byte) _terrainLock.Radius); - packet.AppendByte((byte) _terrainLock.Falloff); - - return packet; - } - } - - public class TerrainLock : Lock { private const int MAX_RADIUS = 5; - - private int _radius; - public TerrainLock(Robot owner,Position location) : base(owner) - { - Location = location; - } - public TerraformType TerraformType { get; set; } public TerraformDirection TerraformDirection { get; set; } @@ -58,6 +19,11 @@ public int Radius public int Falloff { get; set; } + public TerrainLock(Robot owner,Position location) : base(owner) + { + Location = location; + } + public override void AcceptVisitor(ILockVisitor visitor) { visitor.VisitTerrainLock(this); @@ -68,9 +34,12 @@ public override void AcceptVisitor(ILockVisitor visitor) public override bool Equals(Lock other) { if (base.Equals(other)) + { return true; + } var terrainLockTarget = other as TerrainLock; + return terrainLockTarget != null && Equals(Location, terrainLockTarget.Location); } } diff --git a/src/Perpetuum/Zones/Locking/Locks/TerrainLockParametersPacketBuilder.cs b/src/Perpetuum/Zones/Locking/Locks/TerrainLockParametersPacketBuilder.cs new file mode 100644 index 000000000..b8ac3d8a9 --- /dev/null +++ b/src/Perpetuum/Zones/Locking/Locks/TerrainLockParametersPacketBuilder.cs @@ -0,0 +1,27 @@ +using Perpetuum.Builders; + +namespace Perpetuum.Zones.Locking.Locks +{ + public class TerrainLockParametersPacketBuilder : IBuilder + { + private readonly TerrainLock _terrainLock; + + public TerrainLockParametersPacketBuilder(TerrainLock terrainLock) + { + _terrainLock = terrainLock; + } + + public Packet Build() + { + var packet = new Packet(ZoneCommand.TerrainLockParameters); + + packet.AppendLong(_terrainLock.Id); + packet.AppendByte((byte)_terrainLock.TerraformType); + packet.AppendByte((byte)_terrainLock.TerraformDirection); + packet.AppendByte((byte)_terrainLock.Radius); + packet.AppendByte((byte)_terrainLock.Falloff); + + return packet; + } + } +} diff --git a/src/Perpetuum/Zones/Locking/UnitProperties/LockingTimeProperty.cs b/src/Perpetuum/Zones/Locking/UnitProperties/LockingTimeProperty.cs new file mode 100644 index 000000000..2164eafa4 --- /dev/null +++ b/src/Perpetuum/Zones/Locking/UnitProperties/LockingTimeProperty.cs @@ -0,0 +1,27 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Units; +using Perpetuum.Zones.Blobs; + +namespace Perpetuum.Zones.Locking.UnitProperties +{ + public class LockingTimeProperty : UnitProperty + { + public LockingTimeProperty(Unit owner) + : base(owner, AggregateField.locking_time, + AggregateField.locking_time_modifier, + AggregateField.effect_sensor_booster_locking_time_modifier, + AggregateField.effect_sensor_dampener_locking_time_modifier, + AggregateField.effect_locking_time_modifier) + { } + + protected override double CalculateValue() + { + var v = base.CalculateValue(); + + var blobableUnit = owner as IBlobableUnit; + blobableUnit?.BlobHandler.ApplyBlobPenalty(ref v, -5); + + return v; + } + } +} diff --git a/src/Perpetuum/Zones/Locking/UnitProperties/MaxLockedTargetsProperty.cs b/src/Perpetuum/Zones/Locking/UnitProperties/MaxLockedTargetsProperty.cs new file mode 100644 index 000000000..29c84ff03 --- /dev/null +++ b/src/Perpetuum/Zones/Locking/UnitProperties/MaxLockedTargetsProperty.cs @@ -0,0 +1,31 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Units; +using System; + +namespace Perpetuum.Zones.Locking.UnitProperties +{ + public class MaxLockedTargetsProperty : UnitProperty + { + public MaxLockedTargetsProperty(Unit owner) : base(owner, AggregateField.locked_targets_max) + { + } + + protected override double CalculateValue() + { + var v = base.CalculateValue(); + + if (owner.GetCharacter() == Character.None) + { + return v; + } + + var lockedTargetsMaxBonus = owner.GetPropertyModifier(AggregateField.locked_targets_max_bonus); + + v = Math.Min(lockedTargetsMaxBonus.Value + 1, v); + + return v; + } + } +} diff --git a/src/Perpetuum/Zones/Locking/UnitProperties/MaxTargetingRangeProperty.cs b/src/Perpetuum/Zones/Locking/UnitProperties/MaxTargetingRangeProperty.cs new file mode 100644 index 000000000..b85d3e82d --- /dev/null +++ b/src/Perpetuum/Zones/Locking/UnitProperties/MaxTargetingRangeProperty.cs @@ -0,0 +1,27 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Units; +using Perpetuum.Zones.Blobs; + +namespace Perpetuum.Zones.Locking.UnitProperties +{ + public class MaxTargetingRangeProperty : UnitProperty + { + public MaxTargetingRangeProperty(Unit owner) + : base(owner, AggregateField.locking_range, + AggregateField.locking_range_modifier, + AggregateField.effect_sensor_booster_locking_range_modifier, + AggregateField.effect_sensor_dampener_locking_range_modifier, + AggregateField.effect_locking_range_modifier) + { } + + protected override double CalculateValue() + { + var v = base.CalculateValue(); + var blobableUnit = owner as IBlobableUnit; + + blobableUnit?.BlobHandler.ApplyBlobPenalty(ref v, 0.5); + + return v; + } + } +} diff --git a/src/Perpetuum/Zones/Movements/WaypointMovement.cs b/src/Perpetuum/Zones/Movements/WaypointMovement.cs index 5e4879401..f1403735c 100644 --- a/src/Perpetuum/Zones/Movements/WaypointMovement.cs +++ b/src/Perpetuum/Zones/Movements/WaypointMovement.cs @@ -24,9 +24,22 @@ public override void Start(Unit unit) unit.Direction = unit.CurrentPosition.DirectionTo(_target); unit.CurrentSpeed = 1.0; - _velocity = Vector2.Subtract(_target,unit.CurrentPosition.ToVector2()); - _distance = Vector2.Abs(_velocity); - _velocity = Vector2.Normalize(_velocity); + // Vector to destination + var path = Vector2.Subtract(_target, unit.CurrentPosition.ToVector2()); + // The absolute path along each coordinate + _distance = Vector2.Abs(path); + if (path.Length().IsApproximatelyEqual(0.0f)) + { + // If the path is zero, then is already at the destination + Arrived = true; + // Velocity vector in the direction of the unit. + _velocity = MathHelper.DirectionToVector(unit.Direction); + } + else + { + // Velocity vector in the direction of the path. + _velocity = Vector2.Normalize(path); + } base.Start(unit); } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/ActiveAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/ActiveAI.cs new file mode 100644 index 000000000..92a3aa2c2 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/ActiveAI.cs @@ -0,0 +1,138 @@ +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class ActiveAI : TurretAI + { + private readonly Turret _turret; + private readonly List _moduleActivators; + private readonly TimeSpan _minCycleTime; + private readonly IntervalTimer _primarySelectTimer = new IntervalTimer(0); + + public ActiveAI(Turret turret) : base(turret) + { + _turret = turret; + + _moduleActivators = new List(); + + _minCycleTime = TimeSpan.FromSeconds(5); + + foreach (var module in _turret.ActiveModules) + { + _moduleActivators.Add(new ModuleActivator(module)); + _minCycleTime = _minCycleTime.Min(module.CycleTime); + } + } + + public override void Enter() + { + foreach (var unitVisibility in _turret.GetVisibleUnits()) + { + _turret.LockHostile(unitVisibility.Target); + } + + base.Enter(); + } + + public override void Exit() + { + _turret.StopAllModules(); + _turret.ResetLocks(); + base.Exit(); + } + + public override void AttackHostile(Unit unit) + { + if (!_turret.IsHostile(unit)) + { + return; + } + + _turret.LockHostile(unit); + base.AttackHostile(unit); + } + + public override void Update(TimeSpan time) + { + if (!SelectPrimaryTarget(time)) + { + return; + } + + foreach (var activator in _moduleActivators) + { + activator.Update(time); + } + + base.Update(time); + } + + private bool SelectPrimaryTarget(TimeSpan time) + { + var locks = _turret.GetLocks().Where(l => l.State == LockState.Locked).ToArray(); + + if (locks.Length <= 0) + { + return false; + } + + _primarySelectTimer.Update(time); + + if (_primarySelectTimer.Passed) + { + _primarySelectTimer.Interval = FastRandom.NextTimeSpan(_minCycleTime); + + var validLocks = new List(); + + foreach (var l in locks) + { + var unitLock = (UnitLock)l; + + if (unitLock.Primary) + { + continue; + } + + var visibility = _turret.GetVisibility(unitLock.Target); + + if (visibility == null) + { + continue; + } + + var r = visibility.GetLineOfSight(false); + + if (r != null && r.hit && (r.blockingFlags & BlockingFlags.Plant) == 0) + { + continue; + } + + validLocks.Add(unitLock); + } + + if (validLocks.Count > 0) + { + var newPrimary = validLocks.RandomElement(); + + _turret.SetPrimaryLock(newPrimary); + + return true; + } + } + + return locks.Any(l => l.Primary); + } + + public override void ToActiveAI() + { + // nem csinal semmit + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs new file mode 100644 index 000000000..13811aa2a --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs @@ -0,0 +1,39 @@ +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class AggressorAI : CombatAI + { + public AggressorAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Exit() + { + this.source?.Cancel(); + + base.Exit(); + } + + public override void Update(TimeSpan time) + { + if (!smartCreature.IsInHomeRange) + { + smartCreature.AI.Push(new HomingAI(smartCreature)); + + return; + } + + if (!smartCreature.ThreatManager.IsThreatened) + { + ReturnToHomePosition(); + + return; + } + + this.UpdateHostile(time); + + base.Update(time); + } + + protected override void ToAggressorAI() { } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs new file mode 100644 index 000000000..520161bd3 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs @@ -0,0 +1,63 @@ +using Perpetuum.Log; +using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.NpcSystem.AI.CombatDrones; +using System; +using System.Diagnostics; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public abstract class BaseAI : IState + { + protected readonly SmartCreature smartCreature; + + protected BaseAI(SmartCreature smartCreature) + { + this.smartCreature = smartCreature; + } + + public virtual void Enter() + { + WriteLog("enter state = " + GetType().Name); + } + + public virtual void Exit() + { + smartCreature.StopMoving(); + WriteLog("exit state = " + GetType().Name); + } + + public abstract void Update(TimeSpan time); + + protected virtual void ToHomeAI() + { + this.smartCreature.AI.Push(new HomingAI(smartCreature)); + } + + protected virtual void ToAggressorAI() + { + if (this.smartCreature.Behavior.Type == BehaviorType.Passive) + { + return; + } + + this.smartCreature.AI.Push(new AggressorAI(smartCreature)); + } + + protected virtual void ToAttackCombatDroneAI() + { + this.smartCreature.AI.Push(new AttackCombatDroneAI(smartCreature)); + } + + [Conditional("DEBUG")] + protected void WriteLog(string message) + { + Logger.DebugInfo($"SmartCreatureAI: {message}"); + } + + protected virtual void ToEscortCombatDroneAI() + { + this.smartCreature.AI.Push(new EscortCombatDroneAI(smartCreature)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/AggressiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/AggressiveBehavior.cs new file mode 100644 index 000000000..4d701aabf --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/AggressiveBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public class AggressiveBehavior : Behavior + { + public AggressiveBehavior() : base(BehaviorType.Aggressive) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs new file mode 100644 index 000000000..cd5181937 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs @@ -0,0 +1,31 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public class Behavior + { + public BehaviorType Type { get; private set; } + + protected Behavior(BehaviorType type) + { + Type = type; + } + + public static Behavior Create(BehaviorType type) + { + switch (type) + { + case BehaviorType.Neutral: + return new NeutralBehavior(); + case BehaviorType.Aggressive: + return new AggressiveBehavior(); + case BehaviorType.Passive: + return new PassiveBehavior(); + case BehaviorType.RemoteControlledTurret: + return new RemoteControlledTurretBehavior(); + case BehaviorType.RemoteControlledDrone: + return new RemoteControlledDroneBehavior(); + default: + return new PassiveBehavior(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs new file mode 100644 index 000000000..cf90931a3 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs @@ -0,0 +1,11 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public enum BehaviorType + { + Passive, + Neutral, + Aggressive, + RemoteControlledTurret, + RemoteControlledDrone, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/NeutralBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/NeutralBehavior.cs new file mode 100644 index 000000000..64a412026 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/NeutralBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public class NeutralBehavior : Behavior + { + public NeutralBehavior() : base(BehaviorType.Neutral) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/PassiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/PassiveBehavior.cs new file mode 100644 index 000000000..43e695fb2 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/PassiveBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public class PassiveBehavior : Behavior + { + public PassiveBehavior() : base(BehaviorType.Passive) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledDroneBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledDroneBehavior.cs new file mode 100644 index 000000000..dbb94b943 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledDroneBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public class RemoteControlledDroneBehavior : Behavior + { + public RemoteControlledDroneBehavior() : base(BehaviorType.RemoteControlledDrone) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledTurretBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledTurretBehavior.cs new file mode 100644 index 000000000..3e2ca33c9 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledTurretBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors +{ + public class RemoteControlledTurretBehavior : Behavior + { + public RemoteControlledTurretBehavior() : base(BehaviorType.RemoteControlledTurret) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs new file mode 100644 index 000000000..775f3fcdb --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -0,0 +1,155 @@ +using Perpetuum.EntityFramework; +using Perpetuum.Modules.Weapons; +using Perpetuum.Players; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Units; +using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.NpcSystem.ThreatManaging; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Teleporting; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class BodyPullThreatHelper : + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor + { + private readonly SmartCreature smartCreature; + + public BodyPullThreatHelper(SmartCreature smartCreature) + { + this.smartCreature = smartCreature; + } + + public void Visit(Player player) + { + if (smartCreature.Behavior.Type != BehaviorType.Aggressive && + smartCreature.Behavior.Type != BehaviorType.RemoteControlledTurret && + smartCreature.Behavior.Type != BehaviorType.RemoteControlledDrone) + { + return; + } + + if (player.HasTeleportSicknessEffect) + { + return; + } + + if (smartCreature is RemoteControlledCreature remoteControlledCreature && + (remoteControlledCreature.CommandRobot is Player) && + player.Zone.Configuration.IsAlpha && + !player.HasPvpEffect && + !remoteControlledCreature.CommandRobot.HasPvpEffect) + { + return; + } + + if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == player.Eid)) + { + return; + } + + if (!smartCreature.IsInAggroRange(player)) + { + return; + } + + double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + + smartCreature.AddThreat(player, new Threat(ThreatType.Bodypull, threat)); + } + + public void Visit(AreaBomb bomb) + { + if (!smartCreature.IsInAggroRange(bomb)) + { + return; + } + + if (!smartCreature.ActiveModules.Any(m => m is WeaponModule)) + { + return; + } + + Flocks.ISmartCreatureGroup group = smartCreature.Group; + + if (group != null && group.Members.Any(m => m.ThreatManager.Contains(bomb))) + { + return; + } + + double threat = Threat.BODY_PULL; + + if (smartCreature.ThreatManager.IsThreatened) + { + Hostile h = smartCreature.ThreatManager.GetMostHatedHostile(); + + if (h != null) + { + threat = h.Threat * 100; + } + } + + smartCreature.AddThreat(bomb, new Threat(ThreatType.Bodypull, threat + FastRandom.NextDouble(0, 5))); + } + + public void Visit(Npc npc) + { + ProcessRcuThreats(npc); + } + + public void Visit(SentryTurret sentryTurret) + { + ProcessRcuThreats(sentryTurret); + } + + public void Visit(CombatDrone combatDrone) + { + ProcessRcuThreats(combatDrone); + } + + public void Visit(Portal portal) + { + ProcessRcuThreats(portal); + } + + public void Visit(MobileTeleport teleport) + { + ProcessRcuThreats(teleport); + } + + private void ProcessRcuThreats(Unit unit) + { + if (smartCreature.Behavior.Type != BehaviorType.RemoteControlledTurret && + smartCreature.Behavior.Type != BehaviorType.RemoteControlledDrone) + { + return; + } + + if (!smartCreature.ActiveModules.Any(m => m is WeaponModule)) + { + return; + } + + if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == unit.Eid)) + { + return; + } + + if (!smartCreature.IsInAggroRange(unit)) + { + return; + } + + double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + + smartCreature.AddThreat(unit, new Threat(ThreatType.Bodypull, threat)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs new file mode 100644 index 000000000..8b2944c1b --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -0,0 +1,499 @@ +using Perpetuum.Collections; +using Perpetuum.Modules.Weapons; +using Perpetuum.PathFinders; +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Zones.Locking; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Movements; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using Perpetuum.Zones.NpcSystem.ThreatManaging; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class CombatAI : BaseAI + { + private const int UpdateFrequency = 1650; + private const int Sqrt2 = 141; + private const int Weight = 1000; + // Timer for periodically checking the main hostile target. + private readonly IntervalTimer updateHostileTimer = new IntervalTimer(UpdateFrequency, true); + private readonly IntervalTimer processHostilesTimer = new IntervalTimer(UpdateFrequency); + private readonly IntervalTimer primarySelectTimer = new IntervalTimer(UpdateFrequency); + private List moduleActivators; + private TimeSpan hostilesUpdateFrequency = TimeSpan.FromMilliseconds(UpdateFrequency); + private CombatPrimaryLockSelectionStrategySelector stratSelector; + private Position lastTargetPosition; + private PathMovement movement; + private PathMovement nextMovement; + + public CancellationTokenSource source; + + public bool IsNpcHasMissiles { get; set; } = false; + + public CombatAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Enter() + { + stratSelector = InitSelector(); + moduleActivators = this.smartCreature.ActiveModules + .Select(m => new ModuleActivator(m)) + .ToList(); + IsNpcHasMissiles = this.smartCreature.ActiveModules + .OfType() + .Any(); + processHostilesTimer.Update(hostilesUpdateFrequency); + primarySelectTimer.Update(hostilesUpdateFrequency); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + UpdateHostiles(time); + UpdatePrimaryTarget(time); + RunModules(time); + } + + protected virtual CombatPrimaryLockSelectionStrategySelector InitSelector() + { + return CombatPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(CombatPrimaryLockSelectionStrategy.Hostile, 9) + .WithStrategy(CombatPrimaryLockSelectionStrategy.Random, 1) + .Build(); + } + + protected void UpdateHostiles(TimeSpan time) + { + processHostilesTimer.Update(time); + + if (processHostilesTimer.Passed) + { + processHostilesTimer.Reset(); + ProcessHostiles(); + } + } + + protected void UpdatePrimaryTarget(TimeSpan time) + { + primarySelectTimer.Update(time); + + if (primarySelectTimer.Passed) + { + var success = SelectPrimaryTarget(); + SetPrimaryUpdateDelay(success); + } + } + + protected void RunModules(TimeSpan time) + { + foreach (var activator in moduleActivators) + { + activator.Update(time); + } + } + + protected virtual TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10)); + } + + protected virtual void SetPrimaryUpdateDelay(bool newPrimary) + { + if (newPrimary) + { + primarySelectTimer.Interval = SetPrimaryDwellTime(); + } + else if (GetValidLocks().Length > 0) + { + primarySelectTimer.Interval = TimeSpan.FromSeconds(1); + } + else if (this.smartCreature.GetLocks().Count > 0) + { + primarySelectTimer.Interval = TimeSpan.FromSeconds(1.5); + } + else + { + primarySelectTimer.Interval = TimeSpan.FromSeconds(3.5); + } + } + + protected bool IsAttackable(Hostile hostile) + { + if (!hostile.Unit.InZone) + { + return false; + } + + if (hostile.Unit.States.Dead) + { + return false; + } + + if (!hostile.Unit.IsLockable) + { + return false; + } + + if (hostile.Unit.IsAttackable != ErrorCodes.NoError) + { + return false; + } + + if (hostile.Unit.IsInvulnerable) + { + return false; + } + + if (this.smartCreature.Behavior.Type == BehaviorType.Neutral && hostile.IsExpired) + { + return false; + } + + var isVisible = this.smartCreature.IsVisible(hostile.Unit); + + if (!isVisible) + { + return false; + } + + return true; + } + + protected virtual void ProcessHostiles() + { + var hostileEnumerator = this.smartCreature.ThreatManager.Hostiles.GetEnumerator(); + + while (hostileEnumerator.MoveNext()) + { + var hostile = hostileEnumerator.Current; + + if (!IsAttackable(hostile)) + { + this.smartCreature.ThreatManager.Remove(hostile); + this.smartCreature.AddPseudoThreat(hostile.Unit); + + continue; + } + + if (!this.smartCreature.IsInLockingRange(hostile.Unit)) + { + continue; + } + + SetLockForHostile(hostile); + } + } + + protected bool TryMakeFreeLockSlotFor(Hostile hostile) + { + if (this.smartCreature.HasFreeLockSlot) + { + return true; + } + + this.smartCreature.ThreatManager.Hostiles + .Where(x => x.Threat == 0) + .ForEach(x => this.smartCreature.GetLockByUnit(x.Unit).Cancel()); + + var weakestLock = this.smartCreature.ThreatManager.Hostiles + .SkipWhile(x => x != hostile) + .Skip(1) + .Select(x => this.smartCreature.GetLockByUnit(x.Unit)) + .LastOrDefault(); + + if (weakestLock == null) + { + return false; + } + + weakestLock.Cancel(); + + return true; + } + + protected Hostile GetPrimaryOrMostHatedHostile() + { + var primaryHostile = GetPrimaryHostile(); + + if (primaryHostile != null) + { + return primaryHostile; + } + + return this.smartCreature.ThreatManager.GetMostHatedHostile(); + } + + protected Hostile GetPrimaryHostile() + { + return this.smartCreature.ThreatManager.Hostiles + .Where(h => h.Unit == (this.smartCreature.GetPrimaryLock() as UnitLock)?.Target) + .FirstOrDefault(); + } + + protected virtual void ReturnToHomePosition() + { + smartCreature.AI.Pop(); + smartCreature.AI.Push(new HomingAI(smartCreature)); + this.WriteLog("Enter evade mode."); + } + + protected Task> FindNewAttackPositionAsync(Unit hostile) + { + this.source?.Cancel(); + this.source = new CancellationTokenSource(); + + return Task.Run(() => FindNewAttackPosition(hostile, this.source.Token), this.source.Token); + } + + protected void UpdateHostile(TimeSpan time, bool moveThreatToPseudoThreat = true) + { + var mostHated = GetPrimaryOrMostHatedHostile(); + + if (mostHated == null) + { + return; + } + + var forceCheckPrimary = false; + updateHostileTimer.Update(time); + if (updateHostileTimer.Passed) + { + updateHostileTimer.Reset(); + + // Forced check of the main hostile target. + forceCheckPrimary = movement?.Arrived ?? true; + } + + if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition) || forceCheckPrimary) + { + this.lastTargetPosition = mostHated.Unit.CurrentPosition; + + var findNewTargetPosition = false; + + if (!smartCreature.IsInRangeOf3D(mostHated.Unit, smartCreature.BestActionRange)) + { + findNewTargetPosition = true; + } + else + { + var visibility = smartCreature.GetVisibility(mostHated.Unit); + + if (visibility != null) + { + var r = visibility.GetLineOfSight(this.IsNpcHasMissiles); + + if (r.hit) + { + findNewTargetPosition = true; + } + } + } + + if (findNewTargetPosition) + { + FindNewAttackPositionAsync(mostHated.Unit).ContinueWith(t => + { + if (t.IsCanceled) + { + return; + } + + var path = t.Result; + + if (path == null) + { + if (moveThreatToPseudoThreat) + { + smartCreature.ThreatManager.Remove(mostHated); + smartCreature.AddPseudoThreat(mostHated.Unit); + } + + return; + } + + Interlocked.Exchange(ref this.nextMovement, new PathMovement(path)); + }); + } + } + + if (this.nextMovement != null) + { + movement = Interlocked.Exchange(ref this.nextMovement, null); + movement.Start(this.smartCreature); + } + + this.movement?.Update(this.smartCreature, time); + } + + private void SetLockForHostile(Hostile hostile) + { + var mostHated = GetPrimaryOrMostHatedHostile() == hostile; + var combatLock = this.smartCreature.GetLockByUnit(hostile.Unit); + + if (combatLock == null) + { + if (TryMakeFreeLockSlotFor(hostile)) + { + this.smartCreature.AddLock(hostile.Unit, mostHated); + } + } + else + { + if (mostHated && !combatLock.Primary) + { + this.smartCreature.SetPrimaryLock(combatLock.Id); + } + } + } + + private bool IsLockValidTarget(UnitLock unitLock) + { + if (unitLock == null || unitLock.State != LockState.Locked) + { + return false; + } + + var visibility = this.smartCreature.GetVisibility(unitLock.Target); + + if (visibility == null) + { + return false; + } + + var r = visibility.GetLineOfSight(IsNpcHasMissiles); + + if (r != null && r.hit && (r.blockingFlags & BlockingFlags.Plant) == 0) + { + return false; + } + + return unitLock.Target.GetDistance(this.smartCreature) < this.smartCreature.MaxActionRange; + } + + private UnitLock[] GetValidLocks() + { + return this.smartCreature + .GetLocks() + .Select(l => (UnitLock)l) + .Where(u => IsLockValidTarget(u)) + .ToArray(); + } + + private bool SelectPrimaryTarget() + { + var validLocks = GetValidLocks(); + + if (validLocks.Length < 1) + { + return false; + } + + return stratSelector?.TryUseStrategy(smartCreature, validLocks) ?? false; + } + + private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) + { + var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestActionRange - 1).ToPoint(); + + smartCreature.StopMoving(); + // Nulling movement so that the unit does not resume it at zero speed if the path is not found. + movement = null; + + var maxNode = Math.Pow(smartCreature.HomeRange, 2) * Math.PI; + var priorityQueue = new PriorityQueue((int)maxNode); + var startNode = new Node(smartCreature.CurrentPosition); + + priorityQueue.Enqueue(startNode); + + var closed = new HashSet + { + startNode.position + }; + + Node current; + + while (priorityQueue.TryDequeue(out current)) + { + if (cancellationToken.IsCancellationRequested) + { + return null; + } + + if (IsValidAttackPosition(hostile, current.position)) + { + return BuildPath(current); + } + + foreach (var n in current.position.GetNeighbours()) + { + if (closed.Contains(n)) + { + continue; + } + + closed.Add(n); + + if (!smartCreature.IsWalkable(n.X, n.Y)) + { + continue; + } + + if (!n.IsInRange(smartCreature.HomePosition, smartCreature.HomeRange)) + { + continue; + } + + var newG = current.g + (n.X - current.position.X == 0 || n.Y - current.position.Y == 0 ? 100 : Sqrt2); + var newH = Heuristic.Manhattan.Calculate(n.X, n.Y, end.X, end.Y) * Weight; + var newNode = new Node(n) + { + g = newG, + f = newG + newH, + parent = current + }; + + priorityQueue.Enqueue(newNode); + } + } + + return null; + } + + private bool IsValidAttackPosition(Unit hostile, Point position) + { + var position3 = smartCreature.Zone.FixZ(position.ToPosition()).AddToZ(smartCreature.Height); + + if (!hostile.CurrentPosition.IsInRangeOf3D(position3, smartCreature.BestActionRange)) + { + return false; + } + + var r = smartCreature.Zone.IsInLineOfSight(position3, hostile, false); + + return !r.hit; + } + + private static List BuildPath(Node current) + { + var stack = new Stack(); + var node = current; + + while (node != null) + { + stack.Push(node.position); + node = node.parent; + } + + return stack.ToList(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs new file mode 100644 index 000000000..0541705db --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs @@ -0,0 +1,47 @@ +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI.CombatDrones +{ + public class AttackCombatDroneAI : CombatAI + { + public AttackCombatDroneAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override CombatPrimaryLockSelectionStrategySelector InitSelector() + { + return CombatPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(CombatPrimaryLockSelectionStrategy.HostileOrClosest, 1) + .Build(); + } + + public override void Exit() + { + this.source?.Cancel(); + + base.Exit(); + } + + public override void Update(TimeSpan time) + { + if (!smartCreature.ThreatManager.IsThreatened) + { + ReturnToHomePosition(); + + return; + } + + this.UpdateHostile(time, false); + + base.Update(time); + } + + protected override void ToAggressorAI() { } + + protected override void ReturnToHomePosition() + { + smartCreature.AI.Pop(); + smartCreature.AI.Push(new EscortCombatDroneAI(smartCreature)); + this.WriteLog("Returning to command robot."); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/EscortCombatDroneAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/EscortCombatDroneAI.cs new file mode 100644 index 000000000..8aafc94cd --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/EscortCombatDroneAI.cs @@ -0,0 +1,80 @@ +using Perpetuum.PathFinders; +using Perpetuum.Zones.Movements; +using Perpetuum.Zones.RemoteControl; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI.CombatDrones +{ + public class EscortCombatDroneAI : CombatAI + { + private PathMovement movement; + private readonly double maxReturnGuardRadius; + private readonly PathFinder pathFinder; + + public EscortCombatDroneAI(SmartCreature smartCreature) : base(smartCreature) + { + this.maxReturnGuardRadius = ((this.smartCreature as CombatDrone).GuardRange * 0.4).Clamp(3, 20); + this.pathFinder = new AStarFinder(Heuristic.Manhattan, smartCreature.IsWalkable); + } + + public override void Enter() + { + var randomHome = this.smartCreature.Zone.FindPassablePointInRadius(this.smartCreature.HomePosition, (int)this.maxReturnGuardRadius); + + if (randomHome == default) + { + randomHome = this.smartCreature.HomePosition; + } + + pathFinder + .FindPathAsync(this.smartCreature.CurrentPosition, randomHome) + .ContinueWith(t => + { + var path = t.Result; + + if (path == null) + { + WriteLog("Path not found! (" + this.smartCreature.CurrentPosition + " => " + this.smartCreature.HomePosition + ")"); + + var f = new AStarFinder(Heuristic.Manhattan, (x, y) => true); + + path = f.FindPath(this.smartCreature.CurrentPosition, this.smartCreature.HomePosition); + + if (path == null) + { + WriteLog("Safe path not found! (" + this.smartCreature.CurrentPosition + " => " + this.smartCreature.HomePosition + ")"); + } + } + + this.movement = new PathMovement(path); + this.movement.Start(this.smartCreature); + }); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + if (this.smartCreature.ThreatManager.IsThreatened) + { + this.ToAttackCombatDroneAI(); + + return; + } + + if (this.movement != null) + { + this.movement.Update(this.smartCreature, time); + + if (this.movement.Arrived) + { + this.smartCreature.AI.Pop(); + + return; + } + } + + base.Update(time); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/GuardCombatDroneAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/GuardCombatDroneAI.cs new file mode 100644 index 000000000..4565833ea --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/GuardCombatDroneAI.cs @@ -0,0 +1,44 @@ +using Perpetuum.Zones.Movements; +using Perpetuum.Zones.RemoteControl; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI.CombatDrones +{ + public class GuardCombatDroneAI : BaseAI + { + private RandomMovement movement; + + public GuardCombatDroneAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Enter() + { + this.smartCreature.StopAllModules(); + this.smartCreature.ResetLocks(); + + this.movement = new RandomMovement(this.smartCreature.HomePosition, (this.smartCreature as CombatDrone).GuardRange); + + movement.Start(this.smartCreature); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + if (!(this.smartCreature as CombatDrone).IsInGuardRange) + { + this.ToEscortCombatDroneAI(); + + return; + } + + if (this.smartCreature.ThreatManager.IsThreatened) + { + this.ToAttackCombatDroneAI(); + + return; + } + + this.movement?.Update(this.smartCreature, time); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/HarvestingIndustrialTurretAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/HarvestingIndustrialTurretAI.cs new file mode 100644 index 000000000..8d0fd6cf9 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/HarvestingIndustrialTurretAI.cs @@ -0,0 +1,27 @@ +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class HarvestingIndustrialTurretAI : StationaryIndustrialAI + { + public HarvestingIndustrialTurretAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Update(TimeSpan time) + { + FindIndustrialTargets(time); + + base.Update(time); + } + + public void FindIndustrialTargets(TimeSpan time) + { + UpdateFrequency.Update(time); + + if (UpdateFrequency.Passed) + { + UpdateFrequency.Reset(); + smartCreature.LookingForHarvestingTargets(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/HarvestingTurretIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/HarvestingTurretIndustrialAI.cs new file mode 100644 index 000000000..f418cfa97 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/HarvestingTurretIndustrialAI.cs @@ -0,0 +1,16 @@ +using Perpetuum.Zones.NpcSystem.TargettingStrategies; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class HarvestingTurretIndustrialAI : StationaryIndustrialAI + { + public HarvestingTurretIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() + { + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.MostFertilePlantWithinOptimal, 1) + .Build(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/HomingAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/HomingAI.cs new file mode 100644 index 000000000..81da49e66 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/HomingAI.cs @@ -0,0 +1,75 @@ +using Perpetuum.PathFinders; +using Perpetuum.Zones.Movements; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class HomingAI : CombatAI + { + private PathMovement movement; + private readonly double maxReturnHomeRadius; + private readonly PathFinder pathFinder; + + public HomingAI(SmartCreature smartCreature) : base(smartCreature) + { + this.maxReturnHomeRadius = (smartCreature.HomeRange * 0.4).Clamp(3, 20); + this.pathFinder = new AStarFinder(Heuristic.Manhattan, smartCreature.IsWalkable); + } + + public override void Enter() + { + var randomHome = this.smartCreature.Zone.FindPassablePointInRadius(this.smartCreature.HomePosition, (int)this.maxReturnHomeRadius); + + if (randomHome == default) + { + randomHome = this.smartCreature.HomePosition; + } + + pathFinder + .FindPathAsync(this.smartCreature.CurrentPosition, randomHome) + .ContinueWith(t => + { + var path = t.Result; + + if (path == null) + { + WriteLog("Path not found! (" + this.smartCreature.CurrentPosition + " => " + this.smartCreature.HomePosition + ")"); + + var f = new AStarFinder(Heuristic.Manhattan, (x, y) => true); + + path = f.FindPath(this.smartCreature.CurrentPosition, this.smartCreature.HomePosition); + + if (path == null) + { + WriteLog("Safe path not found! (" + this.smartCreature.CurrentPosition + " => " + this.smartCreature.HomePosition + ")"); + } + } + + this.movement = new PathMovement(path); + this.movement.Start(this.smartCreature); + }); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + if (this.movement != null) + { + this.movement.Update(this.smartCreature, time); + + if (this.movement.Arrived) + { + this.smartCreature.AI.Pop(); + + return; + } + } + + base.Update(time); + } + + protected override void ToHomeAI() { } + protected override void ToAggressorAI() { } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IdleAI.cs new file mode 100644 index 000000000..43e9a5628 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/IdleAI.cs @@ -0,0 +1,43 @@ +using Perpetuum.Zones.Movements; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class IdleAI : BaseAI + { + private RandomMovement movement; + + public IdleAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Enter() + { + this.smartCreature.StopAllModules(); + this.smartCreature.ResetLocks(); + + this.movement = new RandomMovement(this.smartCreature.HomePosition, this.smartCreature.HomeRange); + + movement.Start(this.smartCreature); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + if (!this.smartCreature.IsInHomeRange) + { + this.ToHomeAI(); + + return; + } + + if (this.smartCreature.ThreatManager.IsThreatened) + { + this.ToAggressorAI(); + + return; + } + + this.movement?.Update(this.smartCreature, time); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/InactiveAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/InactiveAI.cs new file mode 100644 index 000000000..9a7e7dbd0 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/InactiveAI.cs @@ -0,0 +1,24 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class InactiveAI : TurretAI + { + private readonly Turret _turret; + + public InactiveAI(Turret turret) : base(turret) + { + _turret = turret; + } + + public override void Enter() + { + _turret.StopAllModules(); + _turret.ResetLocks(); + base.Enter(); + } + + public override void ToInactiveAI() + { + // nem csinal semmit + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs new file mode 100644 index 000000000..a77586071 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -0,0 +1,275 @@ +using Perpetuum.Timers; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using Perpetuum.Zones.NpcSystem.ThreatManaging; +using System; +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.RemoteControl; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class IndustrialAI : BaseAI + { + private const int UpdateFrequency = 1650; + private const int EjectFrequency = 300000; + private const double VolumeToEject = 10.0; + private readonly IntervalTimer processIndustrialTargetsTimer = new IntervalTimer(UpdateFrequency); + private readonly IntervalTimer processEjectTimer = new IntervalTimer(EjectFrequency); + private IntervalTimer primarySelectTimer; + private List moduleActivators; + private TimeSpan industrialTargetsUpdateFrequency = TimeSpan.FromMilliseconds(UpdateFrequency); + private TimeSpan ejectCargoFrequency = TimeSpan.FromMilliseconds(EjectFrequency); + private IndustrialPrimaryLockSelectionStrategySelector stratSelector; + + public IndustrialAI(SmartCreature smartCreature) : base(smartCreature) + { + primarySelectTimer = new IntervalTimer((this.smartCreature.ActiveModules.Max(x => x?.CycleTime.Milliseconds) ?? 0) + UpdateFrequency); + } + + public override void Enter() + { + stratSelector = InitSelector(); + moduleActivators = this.smartCreature.ActiveModules + .Select(m => new ModuleActivator(m)) + .ToList(); + processIndustrialTargetsTimer.Update(industrialTargetsUpdateFrequency); + processEjectTimer.Update(ejectCargoFrequency); + primarySelectTimer.Update(industrialTargetsUpdateFrequency); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + UpdateIndustrialTargets(time); + UpdatePrimaryTarget(time); + RunModules(time); + EjectCargo(time); + } + + protected virtual IndustrialPrimaryLockSelectionStrategySelector InitSelector() + { + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTile, 9) + .Build(); + } + + protected void UpdateIndustrialTargets(TimeSpan time) + { + processIndustrialTargetsTimer.Update(time); + + if (processIndustrialTargetsTimer.Passed) + { + processIndustrialTargetsTimer.Reset(); + ProcessIndustrialTargets(); + } + } + + protected void UpdatePrimaryTarget(TimeSpan time) + { + primarySelectTimer.Update(time); + + if (primarySelectTimer.Passed) + { + var success = SelectPrimaryTarget(); + + SetPrimaryUpdateDelay(success); + } + } + + protected void RunModules(TimeSpan time) + { + foreach (var activator in moduleActivators) + { + activator.Update(time); + } + } + + protected void EjectCargo(TimeSpan time) + { + processEjectTimer.Update(time); + + if (processEjectTimer.Passed) + { + processEjectTimer.Reset(); + (smartCreature as IndustrialTurret).EjectCargo(smartCreature.Zone); + } + } + + protected virtual TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10)); + } + + protected virtual void SetPrimaryUpdateDelay(bool newPrimary) + { + if (newPrimary) + { + primarySelectTimer.Interval = SetPrimaryDwellTime(); + } + else if (GetValidLocks().Length > 0) + { + primarySelectTimer.Interval = TimeSpan.FromSeconds(1); + } + else if (this.smartCreature.GetLocks().Count > 0) + { + primarySelectTimer.Interval = TimeSpan.FromSeconds(1.5); + } + else + { + primarySelectTimer.Interval = TimeSpan.FromSeconds(3.5); + } + } + + protected bool IsAttackable(Hostile hostile) + { + if (!hostile.Unit.InZone) + { + return false; + } + + if (hostile.Unit.States.Dead) + { + return false; + } + + if (!hostile.Unit.IsLockable) + { + return false; + } + + if (hostile.Unit.IsAttackable != ErrorCodes.NoError) + { + return false; + } + + if (hostile.Unit.IsInvulnerable) + { + return false; + } + + if (this.smartCreature.Behavior.Type == BehaviorType.Neutral && hostile.IsExpired) + { + return false; + } + + var isVisible = this.smartCreature.IsVisible(hostile.Unit); + + if (!isVisible) + { + return false; + } + + return true; + } + + protected virtual void ProcessIndustrialTargets() + { + if (!this.smartCreature.IndustrialValueManager.IndustrialTargets.Any()) + { + if (this.smartCreature.GetLocks().Any()) + { + this.smartCreature.ResetLocks(); + } + + return; + } + + var industrialTargetsEnumerator = this.smartCreature.IndustrialValueManager.IndustrialTargets.GetEnumerator(); + + while (industrialTargetsEnumerator.MoveNext()) + { + var industrialTarget = industrialTargetsEnumerator.Current; + + if (!this.smartCreature.IsInLockingRange(industrialTarget.Position)) + { + continue; + } + + SetLockForIndustrialTarget(industrialTarget); + } + } + + protected bool TryMakeFreeLockSlotFor(IndustrialTarget industrialTarget) + { + if (this.smartCreature.HasFreeLockSlot) + { + return true; + } + + this.smartCreature.GetSecondaryLocks().ForEach(x => x.Cancel()); + + return true; + } + + protected IndustrialTarget GetMostValuableIndustrialTarget() + { + var primaryTarget = this.smartCreature.IndustrialValueManager.IndustrialTargets + .Where(h => h.Position == (this.smartCreature.GetPrimaryLock() as TerrainLock)?.Location) + .FirstOrDefault(); + + if (primaryTarget != null) + { + return primaryTarget; + } + + return this.smartCreature.IndustrialValueManager.GetMostValuableIndustrialTarget(); + } + + private void SetLockForIndustrialTarget(IndustrialTarget industrialTarget) + { + var mostValuable = GetMostValuableIndustrialTarget() == industrialTarget; + var industrialLock = this.smartCreature.GetLockByPosition(industrialTarget.Position); + + if (industrialLock == null) + { + if (TryMakeFreeLockSlotFor(industrialTarget)) + { + this.smartCreature.AddLock(industrialTarget.Position, mostValuable); + } + } + else + { + if (mostValuable && !industrialLock.Primary) + { + this.smartCreature.SetPrimaryLock(industrialLock.Id); + } + } + } + + private bool IsLockValidTarget(TerrainLock industrialLock) + { + if (industrialLock == null || industrialLock.State != LockState.Locked) + { + return false; + } + + return industrialLock.Location.IsInRangeOf2D(this.smartCreature.PositionWithHeight, this.smartCreature.MaxActionRange); + } + + private TerrainLock[] GetValidLocks() + { + return this.smartCreature + .GetLocks() + .Select(l => (TerrainLock)l) + .Where(u => IsLockValidTarget(u)) + .ToArray(); + } + + private bool SelectPrimaryTarget() + { + var validLocks = GetValidLocks(); + + if (validLocks.Length < 1) + { + return false; + } + + return stratSelector?.TryUseStrategy(smartCreature, validLocks) ?? false; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/MiningIndustrialTurretAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/MiningIndustrialTurretAI.cs new file mode 100644 index 000000000..4153946e0 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/MiningIndustrialTurretAI.cs @@ -0,0 +1,27 @@ +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class MiningIndustrialTurretAI : StationaryIndustrialAI + { + public MiningIndustrialTurretAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Update(TimeSpan time) + { + FindIndustrialTargets(time); + + base.Update(time); + } + + public void FindIndustrialTargets(TimeSpan time) + { + UpdateFrequency.Update(time); + + if (UpdateFrequency.Passed) + { + UpdateFrequency.Reset(); + smartCreature.LookingForMiningTargets(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs new file mode 100644 index 000000000..d173755ea --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs @@ -0,0 +1,413 @@ +using Perpetuum.EntityFramework; +using Perpetuum.Modules; +using Perpetuum.Modules.EffectModules; +using Perpetuum.Modules.Weapons; +using Perpetuum.Timers; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Terrains; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class ModuleActivator : + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor + { + private const double ENERGY_INJECTOR_THRESHOLD = 0.65; + private const double ARMOR_REPAIR_THRESHOLD = 0.95; + private const double ARMOR_REPAIR_CORE_THRESHOLD = 0.35; + private const double SENSOR_DAMPENER_CORE_THRESHOLD = 0.55; + private const double SHIELD_ARMOR_THRESHOLD = 0.35; + private const double SENSOR_JAMMER_CORE_THRESHOLD = 0.55; + private const double WEBBER_CORE_THRESHOLD = 0.55; + private const double PAINTER_CORE_THRESHOLD = 0.65; + private const double BLOBBER_CORE_THRESHOLD = 0.55; + private const double BLINDER_CORE_THRESHOLD = 0.55; + private const double ENERGY_NEUTRALIZER_CORE_THRESHOLD = 0.55; + private const double ENERGY_VAMPIRE_CORE_THRESHOLD = 0.05; + + private readonly IntervalTimer _timer; + private readonly ActiveModule _module; + + public ModuleActivator(ActiveModule module) + { + _module = module; + _timer = new IntervalTimer(TimeSpan.FromSeconds(1), true); + } + + public void Update(TimeSpan time) + { + _ = _timer.Update(time); + + if (!_timer.Passed) + { + return; + } + + _timer.Reset(); + + if (_module.State.Type != ModuleStateType.Idle) + { + return; + } + + _module.AcceptVisitor(this); + } + + public void Visit(MissileWeaponModule module) + { + bool hasShieldEffect = module.ParentRobot.HasShieldEffect; + + if (hasShieldEffect) + { + return; + } + + UnitLock primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); + + if (primaryLock == null) + { + return; + } + + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(primaryLock.Target); + + if (visibility == null) + { + return; + } + + LOSResult result = visibility.GetLineOfSight(true); + + TryActiveModule(result, primaryLock); + } + + public void Visit(WeaponModule module) + { + bool hasShieldEffect = module.ParentRobot.HasShieldEffect; + + if (hasShieldEffect) + { + return; + } + + UnitLock primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); + + if (primaryLock == null) + { + return; + } + + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(primaryLock.Target); + + if (visibility == null) + { + return; + } + + LOSResult result = visibility.GetLineOfSight(false); + + TryActiveModule(result, primaryLock); + } + + public void Visit(ArmorRepairModule module) + { + if (module.ParentRobot.ArmorPercentage >= ARMOR_REPAIR_THRESHOLD) + { + return; + } + + if (module.ParentRobot.CorePercentage < ARMOR_REPAIR_CORE_THRESHOLD) + { + return; + } + + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(ShieldGeneratorModule module) + { + if (module.ParentRobot.ArmorPercentage >= SHIELD_ARMOR_THRESHOLD) + { + return; + } + + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(SensorJammerModule module) + { + if (module.ParentRobot.CorePercentage < SENSOR_JAMMER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(SensorDampenerModule module) + { + if (module.ParentRobot.CorePercentage < SENSOR_DAMPENER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(WebberModule module) + { + if (module.ParentRobot.CorePercentage < WEBBER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(TargetPainterModule module) + { + if (module.ParentRobot.CorePercentage < PAINTER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(BlobEmissionModulatorModule module) + { + if (module.ParentRobot.Zone.Configuration.Protected) + { + return; + } + + if (module.ParentRobot.CorePercentage < BLOBBER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(TargetBlinderModule module) + { + if (module.ParentRobot.CorePercentage < BLINDER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(EnergyNeutralizerModule module) + { + if (module.ParentRobot.CorePercentage < ENERGY_NEUTRALIZER_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(lockTarget.Target); + + if (visibility == null) + { + return; + } + + LOSResult r = visibility.GetLineOfSight(false); + + if (r != null && r.hit) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(EnergyVampireModule module) + { + if (module.ParentRobot.CorePercentage < ENERGY_VAMPIRE_CORE_THRESHOLD) + { + return; + } + + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(lockTarget.Target); + + if (visibility == null) + { + return; + } + + LOSResult r = visibility.GetLineOfSight(false); + + if (r != null && r.hit) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(SensorBoosterModule module) + { + if (module.State.Type == ModuleStateType.Idle) + { + module.State.SwitchTo(ModuleStateType.AutoRepeat); + } + } + + public void Visit(ArmorHardenerModule module) + { + if (module.State.Type == ModuleStateType.Idle) + { + module.State.SwitchTo(ModuleStateType.AutoRepeat); + } + } + + public void Visit(CoreBoosterModule module) + { + if (module.ParentRobot.CorePercentage > ENERGY_INJECTOR_THRESHOLD) + { + return; + } + + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(RemoteControlledDrillerModule module) + { + TerrainLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(RemoteControlledHarvesterModule module) + { + TerrainLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + public void Visit(RemoteControllerModule module) + { + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + + if (lockTarget == null) + { + return; + } + + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(lockTarget.Target); + + if (visibility == null) + { + return; + } + + module.State.SwitchTo(ModuleStateType.Oneshot); + } + + private void TryActiveModule(LOSResult result, UnitLock primaryLock) + { + if (result.hit && !result.blockingFlags.HasFlag(BlockingFlags.Plant)) + { + return; + } + + _module.Lock = primaryLock; + _module.State.SwitchTo(ModuleStateType.Oneshot); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Node.cs b/src/Perpetuum/Zones/NpcSystem/AI/Node.cs new file mode 100644 index 000000000..36cb79c40 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Node.cs @@ -0,0 +1,28 @@ +using System; +using System.Drawing; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class Node : IComparable + { + public readonly Point position; + public Node parent; + public int g; + public int f; + + public Node(Point position) + { + this.position = position; + } + + public int CompareTo(Node other) + { + return f - other.f; + } + + public override int GetHashCode() + { + return position.GetHashCode(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/NullAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/NullAI.cs new file mode 100644 index 000000000..21314aadd --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/NullAI.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class NullAI : TurretAI + { + public NullAI(Turret turret) : base(turret) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs new file mode 100644 index 000000000..f07320916 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs @@ -0,0 +1,16 @@ +using Perpetuum.Zones.NpcSystem.TargettingStrategies; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SentryTurretCombatAI : StationaryCombatAI + { + public SentryTurretCombatAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override CombatPrimaryLockSelectionStrategySelector InitSelector() + { + return CombatPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(CombatPrimaryLockSelectionStrategy.HostileOrClosest, 1) + .Build(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs new file mode 100644 index 000000000..8ba044afa --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs @@ -0,0 +1,19 @@ +using Perpetuum.Zones.NpcSystem.AI.Behaviors; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SentryTurretIdleAI : StationaryIdleAI + { + public SentryTurretIdleAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override void ToAggressorAI() + { + if (this.smartCreature.Behavior.Type == BehaviorType.Passive) + { + return; + } + + this.smartCreature.AI.Push(new SentryTurretCombatAI(this.smartCreature)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs new file mode 100644 index 000000000..812c4e80e --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs @@ -0,0 +1,46 @@ +using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class StationaryCombatAI : CombatAI + { + private readonly IntervalTimer updateFrequency = new IntervalTimer(650); + + public StationaryCombatAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Update(TimeSpan time) + { + FindHostiles(time); + + base.Update(time); + } + + protected override CombatPrimaryLockSelectionStrategySelector InitSelector() + { + return CombatPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(CombatPrimaryLockSelectionStrategy.Hostile, 1) + .WithStrategy(CombatPrimaryLockSelectionStrategy.Closest, 2) + .WithStrategy(CombatPrimaryLockSelectionStrategy.OptimalRange, 3) + .WithStrategy(CombatPrimaryLockSelectionStrategy.Random, 10) + .Build(); + } + + protected override TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + } + + private void FindHostiles(TimeSpan time) + { + updateFrequency.Update(time); + + if (updateFrequency.Passed) + { + updateFrequency.Reset(); + smartCreature.LookingForHostiles(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs new file mode 100644 index 000000000..5ddec3337 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs @@ -0,0 +1,37 @@ +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class StationaryIdleAI : BaseAI + { + public StationaryIdleAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Enter() + { + this.smartCreature.StopAllModules(); + this.smartCreature.ResetLocks(); + base.Enter(); + } + + public override void Update(TimeSpan time) + { + if (this.smartCreature.ThreatManager.IsThreatened) + { + ToAggressorAI(); + } + } + + protected override void ToHomeAI() { } + + protected override void ToAggressorAI() + { + if (this.smartCreature.Behavior.Type == BehaviorType.Passive) + { + return; + } + + this.smartCreature.AI.Push(new StationaryCombatAI(this.smartCreature)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs new file mode 100644 index 000000000..2fc130358 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs @@ -0,0 +1,25 @@ +using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class StationaryIndustrialAI : IndustrialAI + { + public readonly IntervalTimer UpdateFrequency = new IntervalTimer(5000); + + public StationaryIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() + { + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTile, 1) + .Build(); + } + + protected override TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/TurretAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/TurretAI.cs new file mode 100644 index 000000000..a393c0c34 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/TurretAI.cs @@ -0,0 +1,51 @@ +using Perpetuum.StateMachines; +using Perpetuum.Units; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public abstract class TurretAI : IState + { + private readonly Turret _turret; + + protected TurretAI(Turret turret) + { + _turret = turret; + } + + public virtual void Enter() + { + WriteLog("Turret AI:" + GetType().Name); + } + + public virtual void Exit() + { + WriteLog("Turret AI:" + GetType().Name); + } + + public virtual void Update(TimeSpan time) + { + + } + + protected void WriteLog(string message) + { + // Logger.Info(message); + } + + public virtual void ToInactiveAI() + { + _turret.AIStateMachine.ChangeState(new InactiveAI(_turret)); + } + + public virtual void ToActiveAI() + { + _turret.AIStateMachine.ChangeState(new ActiveAI(_turret)); + } + + public virtual void AttackHostile(Unit unit) + { + + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Creature.cs b/src/Perpetuum/Zones/NpcSystem/Creature.cs index 7187db4d9..b9a424829 100644 --- a/src/Perpetuum/Zones/NpcSystem/Creature.cs +++ b/src/Perpetuum/Zones/NpcSystem/Creature.cs @@ -5,6 +5,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.LandMines; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; @@ -14,7 +15,7 @@ public abstract class Creature : Robot { protected override void UpdateUnitVisibility(Unit target) { - if (target is AreaBomb) + if (target is AreaBomb || target is LandMine) { UpdateVisibility(target); } @@ -73,12 +74,13 @@ protected virtual void OnUnitTileChanged(Unit unit) [CanBeNull] public UnitLock SelectOptimalLockTargetFor(ActiveModule module) { + var range = module.OptimalRange + module.Falloff; var locks = GetLocks().OfType().Where(l => { if (l.State != LockState.Locked) return false; - var isInOptimalRange = IsInRangeOf3D(l.Target, module.OptimalRange); + var isInOptimalRange = IsInRangeOf3D(l.Target, range); return isInOptimalRange; }).ToArray(); @@ -94,5 +96,25 @@ public UnitLock SelectOptimalLockTargetFor(ActiveModule module) return locks.RandomElement(); } + + [CanBeNull] + public TerrainLock SelectOptimalLockIndustrialTargetFor(ActiveModule module) + { + var range = module.OptimalRange; + var locks = GetLocks().OfType().Where(industrialLock => + { + if (industrialLock.State != LockState.Locked) + { + return false; + } + + return true; + + }).ToArray(); + + var primaryLock = locks.FirstOrDefault(l => l.Primary); + + return primaryLock; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index 2e576094a..1511955b8 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -1,118 +1,127 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; using Perpetuum.EntityFramework; using Perpetuum.Log; using Perpetuum.Services.Looting; using Perpetuum.Units; using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; namespace Perpetuum.Zones.NpcSystem.Flocks { - public interface INpcGroup - { - string Name { get; } - IEnumerable Members { get; } - void AddDebugInfoToDictionary(IDictionary dictionary); - } - - public delegate Flock FlockFactory(IFlockConfiguration flockConfiguration,Presence presence); + public delegate Flock FlockFactory(IFlockConfiguration flockConfiguration, Presence presence); public class Flock { + private ImmutableList members = ImmutableList.Empty; + public ILootService LootService { get; set; } + public IEntityServices EntityService { get; set; } - private ImmutableList _members = ImmutableList.Empty; + public IFlockConfiguration Configuration { get; } - public IFlockConfiguration Configuration { get;} public Presence Presence { get; } + public NpcBossInfo BossInfo { get { return Configuration.BossInfo; } } + + public bool IsBoss { get { return BossInfo != null; } } + public int Id => Configuration.ID; public int HomeRange => Configuration.HomeRange; + public Position SpawnOrigin { get; } + + public IReadOnlyCollection Members => members; + + public int MembersCount => members.Count; + + public event Action AllMembersDead; + + public event Action NpcCreated; + public Flock(IFlockConfiguration configuration, Presence presence) { Configuration = configuration; Presence = presence; - SpawnOrigin = SpawnOriginSelector(presence); } - public Position SpawnOrigin { get; } - - public IReadOnlyCollection Members => _members; - - private void AddMember(Npc npc) + public virtual IDictionary ToDictionary() { - ImmutableInterlocked.Update(ref _members, m => m.Add(npc)); - npc.Dead += OnMemberDead; - } + var dictionary = new Dictionary + { + {k.ID, Configuration.ID}, + {k.presenceID, Presence.Configuration.ID}, + {k.definition, Configuration.EntityDefault.Definition}, + {k.name, Configuration.Name}, + {k.spawnRangeMin, Configuration.SpawnRange.Min}, + {k.spawnRangeMax, Configuration.SpawnRange.Max}, + {k.flockMemberCount, Configuration.FlockMemberCount}, + {k.respawnSeconds, Configuration.RespawnTime.Seconds}, + {k.homeRange, HomeRange}, + {k.totalSpawnCount, Configuration.TotalSpawnCount}, + {k.spawnOriginX,SpawnOrigin.intX}, + {k.spawnOriginY,SpawnOrigin.intY} + }; - private void RemoveMember(Npc npc) - { - ImmutableInterlocked.Update(ref _members, m => m.Remove(npc)); + return dictionary; } - - protected virtual void OnMemberDead(Unit killer,Unit npc) + public void SpawnAllMembers() { - RemoveMember((Npc) npc); + var totalToSpawn = Configuration.FlockMemberCount - MembersCount; - if (_members.Count <= 0) + for (var i = 0; i < totalToSpawn; i++) { - OnAllMembersDead(); + CreateMemberInZone(); } - } - - public event Action AllMembersDead; - private void OnAllMembersDead() - { - AllMembersDead?.Invoke(this); + Log($"{Configuration.FlockMemberCount} NPCs created"); } - public virtual IDictionary ToDictionary() + public virtual void Update(TimeSpan time) { - var dictionary = new Dictionary { - {k.ID, Configuration.ID}, - {k.presenceID, Presence.Configuration.ID}, - {k.definition, Configuration.EntityDefault.Definition}, - {k.name, Configuration.Name}, - {k.spawnRangeMin, Configuration.SpawnRange.Min}, - {k.spawnRangeMax, Configuration.SpawnRange.Max}, - {k.flockMemberCount, Configuration.FlockMemberCount}, - {k.respawnSeconds, Configuration.RespawnTime.Seconds}, - {k.homeRange, HomeRange}, - {k.totalSpawnCount, Configuration.TotalSpawnCount}, - {k.spawnOriginX,SpawnOrigin.intX}, - {k.spawnOriginY,SpawnOrigin.intY} - }; - return dictionary; } - public int MembersCount => _members.Count; - - public virtual void Update(TimeSpan time) + public override string ToString() { + return $"{Configuration.Name}:{Configuration.ID}"; } - public void SpawnAllMembers() + public void RemoveAllMembersFromZone(bool withTeleportExit = false) { - for (var i = 0; i < Configuration.FlockMemberCount; i++) + foreach (var npc in Members) { - CreateMemberInZone(); - } + if (withTeleportExit) + { + npc.States.Teleport = true; + } - Log($"{Configuration.FlockMemberCount} NPCs created"); + npc.RemoveFromZone(); + RemoveMember(npc); + } } protected virtual void CreateMemberInZone() { var npc = (Npc)EntityService.Factory.Create(Configuration.EntityDefault, EntityIDGenerator.Random); + var zone = Presence.Zone; + var spawnPosition = GetSpawnPosition(SpawnOrigin); + var finder = new ClosestWalkablePositionFinder(zone, spawnPosition, npc); + + if (!finder.Find(out spawnPosition)) + { + Log($"invalid spawnposition in CreateMemberInZone: {spawnPosition} {Configuration.Name} {Presence.Configuration.Name} zone:{zone.Id}"); + } + npc.Behavior = GetBehavior(); + npc.SpecialType = Configuration.SpecialType; + npc.BossInfo = BossInfo; var gen = new CompositeLootGenerator( new LootGenerator(LootService.GetNpcLootInfos(npc.Definition)), @@ -121,17 +130,9 @@ protected virtual void CreateMemberInZone() npc.LootGenerator = gen; npc.HomeRange = HomeRange; - npc.HomePosition = SpawnOrigin; + npc.HomePosition = GetHomePosition(spawnPosition); npc.CallForHelp = Configuration.IsCallForHelp; - var zone = Presence.Zone; - var spawnPosition = GetSpawnPosition(SpawnOrigin); - var finder = new ClosestWalkablePositionFinder(zone, spawnPosition, npc); - if (!finder.Find(out spawnPosition)) - { - Log($"invalid spawnposition in CreateMemberInZone: {spawnPosition} {Configuration.Name} {Presence.Configuration.name} zone:{zone.Id}"); - } - OnNpcCreated(npc); npc.AddToZone(zone, spawnPosition, ZoneEnterType.NpcSpawn); @@ -140,75 +141,98 @@ protected virtual void CreateMemberInZone() Log($"member spawned to zone:{zone.Id} EID:{npc.Eid}"); } - private Position GetSpawnPosition(Position spawnOrigin) + protected virtual void OnMemberDead(Unit killer, Unit npc) + { + RemoveMember((Npc)npc); + + if (members.Count <= 0) + { + OnAllMembersDead(); + } + } + + protected virtual Position GetSpawnPosition(Position spawnOrigin) { var spawnRangeMin = Configuration.SpawnRange.Min; var spawnRangeMax = Configuration.SpawnRange.Max.Min(HomeRange); - var spawnPosition = spawnOrigin.GetRandomPositionInRange2D(spawnRangeMin, spawnRangeMax).Clamp(Presence.Zone.Size); + return spawnPosition; } - public event Action NpcCreated; + protected virtual Position GetHomePosition(Position spawnOrigin) + { + return spawnOrigin; + } protected virtual void OnNpcCreated(Npc npc) { NpcCreated?.Invoke(npc); } - private NpcBehavior GetBehavior() + protected void Log(string message) { - if (Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresence) - { - // hogy ne tamadjanak be mindenkit rogton - return NpcBehavior.Create(NpcBehaviorType.Neutral); - } + Logger.Info($"[Flock] ({ToString()}) - {message}"); + } - return NpcBehavior.Create(Configuration.BehaviorType); + private void AddMember(Npc npc) + { + ImmutableInterlocked.Update(ref members, m => m.Add(npc)); + npc.Dead += OnMemberDead; } - protected void Log(string message) + private void RemoveMember(Npc npc) { - Logger.Info($"[Flock] ({ToString()}) - {message}"); + ImmutableInterlocked.Update(ref members, m => m.Remove(npc)); } - public override string ToString() + private void OnAllMembersDead() { - return $"{Configuration.Name}:{Configuration.ID}"; + AllMembersDead?.Invoke(this); } - public void RemoveAllMembersFromZone(bool withTeleportExit = false) + private Behavior GetBehavior() { - foreach (var npc in Members) - { - if (withTeleportExit) - npc.States.Teleport = true; - npc.RemoveFromZone(); - RemoveMember(npc); + if (Presence is InterzonePresence) + { + return Behavior.Create(Configuration.BehaviorType); + } + else if (Configuration.BehaviorType == BehaviorType.Aggressive && Presence is DynamicPresenceExtended) + { + return Behavior.Create(Configuration.BehaviorType); + } + else if (Configuration.BehaviorType == BehaviorType.Aggressive && Presence is DynamicPresence) + { + return Behavior.Create(BehaviorType.Neutral); } + + return Behavior.Create(Configuration.BehaviorType); } private Position SpawnOriginSelector(Presence presence) { switch (presence) { - case DynamicPresence dp: - { - return dp.DynamicPosition; - } - case RandomPresence rp: - { - return rp.SpawnOriginForRandomPresence; - } - case RoamingPresence roaming: - { - return roaming.SpawnOrigin; - } + case InterzonePresence interzonePresense: + { + return Configuration.SpawnOrigin; + } + case DynamicPresence dynamicPresence: + { + return dynamicPresence.DynamicPosition; + } + case RandomPresence randomPresense: + { + return randomPresense.SpawnOriginForRandomPresence; + } + case IRoamingPresence roaming: + { + return roaming.SpawnOrigin; + } } return Configuration.SpawnOrigin; } - } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs index f3e3d0783..1f1245d31 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs @@ -1,30 +1,49 @@ -using System; using Perpetuum.EntityFramework; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using System; namespace Perpetuum.Zones.NpcSystem.Flocks { public class FlockConfiguration : IFlockConfiguration { public int ID { get; set; } + public string Name { get; set; } + public int PresenceID { get; set; } + public int FlockMemberCount { get; set; } + public Position SpawnOrigin { get; set; } + public IntRange SpawnRange { get; set; } + public int TotalSpawnCount { get; set; } + public string Note { get; set; } + public double RespawnMultiplierLow { get; set; } + public bool IsCallForHelp { get; set; } + public bool Enabled { get; set; } - public NpcBehaviorType BehaviorType { get; set; } + + public BehaviorType BehaviorType { get; set; } + + public NpcSpecialType SpecialType { get; set; } + + public NpcBossInfo BossInfo { get; set; } + public int HomeRange { get; set; } + public EntityDefault EntityDefault { get; set; } + public TimeSpan RespawnTime { get; set; } public FlockConfiguration() { - BehaviorType = NpcBehaviorType.Neutral; - HomeRange = (int) DistanceConstants.MAX_NPC_FLOCK_HOME_RANGE; + BehaviorType = BehaviorType.Neutral; + HomeRange = (int)DistanceConstants.MAX_NPC_FLOCK_HOME_RANGE; } public override string ToString() diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs index ea4ad59ea..f2946f4e8 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -1,27 +1,32 @@ +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; using System.Collections.Generic; using System.Linq; -using Perpetuum.Data; -using Perpetuum.EntityFramework; namespace Perpetuum.Zones.NpcSystem.Flocks { public class FlockConfigurationRepository : IFlockConfigurationRepository { + private readonly NpcBossInfoBuilder _bossBuilder; private readonly FlockConfigurationBuilder.Factory _flockConfigurationBuilderFactory; - private readonly Dictionary _flockConfigurations = new Dictionary(); + private readonly Dictionary _flockConfigurations = new Dictionary(); - public FlockConfigurationRepository(FlockConfigurationBuilder.Factory flockConfigurationBuilderFactory) + public FlockConfigurationRepository(FlockConfigurationBuilder.Factory flockConfigurationBuilderFactory, NpcBossInfoBuilder bossBuilder) { _flockConfigurationBuilderFactory = flockConfigurationBuilderFactory; + _bossBuilder = bossBuilder; } public void LoadAllConfig() { var records = Db.Query().CommandText("select * from npcflock").Execute(); + foreach (var r in records) { var builder = _flockConfigurationBuilderFactory(); + builder.With(c => { c.ID = r.GetValue("id"); @@ -38,9 +43,13 @@ public void LoadAllConfig() c.RespawnMultiplierLow = r.GetValue("respawnmultiplierlow"); c.IsCallForHelp = r.GetValue("iscallforhelp"); c.Enabled = r.GetValue("enabled"); - c.BehaviorType = (NpcBehaviorType) r.GetValue("behaviorType"); + c.BehaviorType = (BehaviorType)r.GetValue("behaviorType"); + c.SpecialType = (NpcSpecialType)r.GetValue("npcSpecialType"); + c.BossInfo = _bossBuilder.GetBossInfoByFlockID(c.ID, c); }); + var config = builder.Build(); + _flockConfigurations[config.ID] = config; } } @@ -66,6 +75,7 @@ public void Insert(IFlockConfiguration item) (name,presenceid,flockmembercount,definition,spawnoriginX,spawnoriginY,spawnrangeMin,spawnrangeMax,respawnseconds,totalspawncount,homerange,note,respawnmultiplierlow) values (@name,@presenceID,@flockMemberCount,@definition,@spawnOriginX,@spawnOriginY,@spawnRangeMin,@spawnRangeMax,@respawnSeconds,@totalSpawnCount,@homeRange,@note,@respawnMultiplierLow); select cast(scope_identity() as int)"; + var id = Db.Query().CommandText(query) .SetParameter("@name", item.Name) .SetParameter("@presenceID", item.PresenceID) @@ -83,7 +93,9 @@ public void Insert(IFlockConfiguration item) .ExecuteScalar().ThrowIfEqual(0, ErrorCodes.SQLInsertError); if (item is FlockConfiguration fc) + { fc.ID = id; + } } public void Update(IFlockConfiguration item) @@ -119,14 +131,17 @@ public void Update(IFlockConfiguration item) .SetParameter("@respawnMultiplierLow", item.RespawnMultiplierLow) .SetParameter("@note", item.Note) .ExecuteNonQuery(); - if ( res == 0 ) + + if (res == 0) + { throw new PerpetuumException(ErrorCodes.SQLUpdateError); + } } public void Delete(IFlockConfiguration item) { Db.Query().CommandText("delete npcflock where id=@ID") - .SetParameter("@ID",item.ID) + .SetParameter("@ID", item.ID) .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLDeleteError); } } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs index eda951e82..643a9fb79 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs @@ -1,24 +1,43 @@ -using System; using Perpetuum.EntityFramework; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using System; namespace Perpetuum.Zones.NpcSystem.Flocks { public interface IFlockConfiguration { int ID { get; } - int PresenceID { get;} + + int PresenceID { get; } + double RespawnMultiplierLow { get; } + int HomeRange { get; } + TimeSpan RespawnTime { get; } + int FlockMemberCount { get; } + int TotalSpawnCount { get; } + string Name { get; } + bool Enabled { get; } + IntRange SpawnRange { get; } + EntityDefault EntityDefault { get; } + Position SpawnOrigin { get; } + string Note { get; } + bool IsCallForHelp { get; } - NpcBehaviorType BehaviorType { get; } + + BehaviorType BehaviorType { get; } + + NpcSpecialType SpecialType { get; } + + NpcBossInfo BossInfo { get; } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/ISmartCreatureGroup.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/ISmartCreatureGroup.cs new file mode 100644 index 000000000..adba578a8 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/ISmartCreatureGroup.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Perpetuum.Zones.NpcSystem.Flocks +{ + public interface ISmartCreatureGroup + { + string Name { get; } + + IEnumerable Members { get; } + + void AddDebugInfoToDictionary(IDictionary dictionary); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index e0a3003e5..8c7be9949 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs @@ -11,8 +11,6 @@ public class NormalFlock : Flock { private readonly ConcurrentQueue _spawnTimes = new ConcurrentQueue(); - private TimeSpan _elapsedTime = TimeSpan.Zero; - public double respawnMultiplierLow; public double respawnMultiplier = 1.0; @@ -66,14 +64,21 @@ protected override void OnMemberDead(Unit killer,Unit npc) base.OnMemberDead(killer,npc); _allowMultiplierChange = true; - _spawnTimes.Enqueue(new TimeTracker(RespawnTime)); + _spawnTimes.Enqueue(new TimeTracker(GetRespawnTime())); } - public override void Update(TimeSpan time) + private TimeSpan GetRespawnTime() { - _elapsedTime += time; + if (IsBoss) + { + return BossInfo.GetNextSpawnTime(RespawnTime); + } + return RespawnTime; + } - if (!Presence.Configuration.isRespawnAllowed) + public override void Update(TimeSpan time) + { + if (!Presence.Configuration.IsRespawnAllowed) return; RespawnAllDeadNpcs(time); @@ -115,6 +120,7 @@ protected override void CreateMemberInZone() if (IsMaxSpawnCountReached) return; + BossInfo?.OnRespawn(); base.CreateMemberInZone(); } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs new file mode 100644 index 000000000..669c7bd0f --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs @@ -0,0 +1,32 @@ +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Flocks +{ + /// + /// This Flock is to be used with the DynamicePresenceExtended class to take advantage of a settable spawn location separate from its HomePosition + /// + public class RemoteSpawningFlock : Flock + { + public RemoteSpawningFlock(IFlockConfiguration configuration, Presence presence) : base(configuration, presence) + { + } + + protected override Position GetSpawnPosition(Position spawnOrigin) + { + if (Presence is DynamicPresenceExtended presence) + { + spawnOrigin = presence.SpawnLocation.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } + + protected override Position GetHomePosition(Position spawnOrigin) + { + if (Presence is DynamicPresenceExtended presence) + { + spawnOrigin = presence.DynamicPosition.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs new file mode 100644 index 000000000..93c2b73fd --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs @@ -0,0 +1,43 @@ +using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; +using System; + +namespace Perpetuum.Zones.NpcSystem.Flocks +{ + /// + /// A NormalFlock that is the child of a RoamingPresence + /// NormalFlocks respawn members on the Update call + /// This class prevents that and allows the IRoamingPresence SpawnState to handle respawning all flocks. + /// + public class RoamingFlock : NormalFlock + { + public RoamingFlock(IFlockConfiguration configuration, Presence presence) : base(configuration, presence) { } + + public override void Update(TimeSpan time) + { + if (IsPresenceInSpawningState()) + return; + + base.Update(time); + } + + protected virtual bool IsPresenceInSpawningState() + { + if (Presence is IRoamingPresence roaming) + { + return roaming.StackFSM.Current is SpawnState; + } + return false; + } + + protected override Position GetSpawnPosition(Position spawnOrigin) + { + if (Presence is IRoamingPresence roaming) + { + spawnOrigin = roaming.SpawnOrigin.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IIndustrialValueManager.cs b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IIndustrialValueManager.cs new file mode 100644 index 000000000..a86776d19 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IIndustrialValueManager.cs @@ -0,0 +1,17 @@ +using System.Collections.Immutable; + +namespace Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement +{ + public interface IIndustrialValueManager + { + bool IsValuable { get; } + + ImmutableSortedSet IndustrialTargets { get; } + + bool Contains(Position tile); + + void Remove(IndustrialTarget industrialTarget); + + void Clear(); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialTarget.cs b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialTarget.cs new file mode 100644 index 000000000..782c576aa --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialTarget.cs @@ -0,0 +1,83 @@ +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem.ThreatManaging; +using System; + +namespace Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement +{ + public class IndustrialTarget : IComparable + { + private static readonly TimeSpan _threatTimeOut = TimeSpan.FromSeconds(30); + + private double _industrialValue; + + public readonly Position Position; + + public TimeSpan LastIndistrialValueUpdate { get; private set; } + + public double IndustrialValue + { + get + { + return _industrialValue; + } + private set + { + if (Math.Abs(_industrialValue - value) <= double.Epsilon) + { + return; + } + + _industrialValue = value; + OnIndustrialValueUpdated(); + } + } + + public bool IsExpired + { + get + { + return (GlobalTimer.Elapsed - LastIndistrialValueUpdate) >= _threatTimeOut; + } + } + + public event Action Updated; + + public IndustrialTarget(Position position) + { + this.Position = position; + IndustrialValue = 0.0; + } + + public void AddIndustrialValue(IndustrialValue industrialValue) + { + if (industrialValue.value <= 0.0) + { + return; + } + + IndustrialValue += industrialValue.value; + } + + private void OnIndustrialValueUpdated() + { + LastIndistrialValueUpdate = GlobalTimer.Elapsed; + Updated?.Invoke(this); + } + + public int CompareTo(IndustrialTarget other) + { + if (other._industrialValue < _industrialValue) + { + return -1; + } + + if (other._industrialValue > _industrialValue) + { + return 1; + } + + return 0; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValue.cs b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValue.cs new file mode 100644 index 000000000..6590887bd --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValue.cs @@ -0,0 +1,28 @@ +namespace Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement +{ + public struct IndustrialValue + { + public const double CommonMineral = 10; + public const double RareMineral = 25; + public const double CommonPlant = 10; + public const double RarePlant = 25; + public readonly IndustrialValueType type; + public readonly double value; + + public IndustrialValue(IndustrialValueType type, double value) + { + this.type = type; + this.value = value; + } + + public override string ToString() + { + return $"{type} = {value}"; + } + + public static IndustrialValue Multiply(IndustrialValue threat, double multiplier) + { + return new IndustrialValue(threat.type, threat.value * multiplier); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueExtensions.cs b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueExtensions.cs new file mode 100644 index 000000000..063e5fb04 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueExtensions.cs @@ -0,0 +1,11 @@ +namespace Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement +{ + public static class IndustrialValueExtensions + { + [CanBeNull] + public static IndustrialTarget GetMostValuableIndustrialTarget(this IIndustrialValueManager manager) + { + return manager.IndustrialTargets.Min; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueManager.cs b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueManager.cs new file mode 100644 index 000000000..8603391f9 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueManager.cs @@ -0,0 +1,73 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement +{ + public class IndustrialValueManager : IIndustrialValueManager + { + private ImmutableDictionary _industrialTargets = ImmutableDictionary.Empty; + + public ImmutableSortedSet IndustrialTargets + { + get { return _industrialTargets.Values.ToImmutableSortedSet(); } + } + + public bool IsValuable + { + get { return !_industrialTargets.IsEmpty; } + } + + public IndustrialTarget GetOrAddIndustrialTargetWithValue(Position tile, IndustrialValueType valueType, double value) + { + return ImmutableInterlocked.GetOrAdd(ref _industrialTargets, tile.ToString(), eid => + { + var industrialTarget = new IndustrialTarget(tile); + + industrialTarget.AddIndustrialValue(new IndustrialValue(valueType, value)); + + return industrialTarget; + }); + } + + public bool Contains(Position tile) + { + return _industrialTargets.ContainsKey(tile.ToString()); + } + + public void Clear() + { + _industrialTargets = _industrialTargets.Clear(); + } + + public void Remove(IndustrialTarget industrialTarget) + { + ImmutableInterlocked.TryRemove(ref _industrialTargets, industrialTarget.Position.ToString(), out _); + } + + public string ToDebugString() + { + if (_industrialTargets.Count == 0) + { + return string.Empty; + } + + var sb = new StringBuilder(); + + sb.AppendLine(); + sb.AppendLine("========== THREAT =========="); + sb.AppendLine(); + + foreach (var industrialTarget in _industrialTargets.Values.OrderByDescending(h => h.IndustrialValue)) + { + sb.AppendFormat(" {0} ({1}) => {2}", "Industrial Target", industrialTarget.Position.ToString(), industrialTarget.IndustrialValue); + sb.AppendLine(); + } + + sb.AppendLine(); + sb.AppendLine("============================"); + + return sb.ToString(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueType.cs b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueType.cs new file mode 100644 index 000000000..5d3144cd6 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueType.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement +{ + public enum IndustrialValueType + { + Undefined, + Mineral, + Plant, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs new file mode 100644 index 000000000..7ec7deafa --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -0,0 +1,277 @@ +using Perpetuum.Players; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Zones.Intrusion; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem +{ + /// + /// Specifies the behavior of a Boss-type NPC with various settings + /// + public class NpcBossInfo + { + private readonly EventListenerService _eventChannel; + private readonly int _id; + private readonly double? _respawnNoiseFactor; + private readonly long? _outpostEID; + private readonly int? _stabilityPts; + private readonly string _deathMsg; + private readonly string _aggroMsg; + private readonly CustomRiftConfig _riftConfig; + private bool _speak; + private bool overrideRelations; + private readonly TimeKeeper _onDamageDebounce = new TimeKeeper(TimeSpan.FromSeconds(5)); + private readonly TimeKeeper _aggroDebounce = new TimeKeeper(TimeSpan.FromSeconds(5)); + + private bool IsOutpostBoss { get { return _outpostEID != null; } } + + private int StabilityPoints { get { return _stabilityPts ?? 0; } } + + private bool HasRiftToSpawn { get { return _riftConfig != null; } } + + public int FlockId { get; } + + public TimeSpan RespawnTime { get; set; } + + public bool IsLootSplit { get; } + + public bool IsDead { get; private set; } + + public bool IsAnnounced { get; private set; } + + public NpcBossInfo( + EventListenerService eventChannel, + int id, + int flockid, + double? respawnNoiseFactor, + bool lootSplit, + long? outpostEID, + int? stabilityPts, + bool overrideRelations, + string customDeathMsg, + string customAggroMsg, + CustomRiftConfig riftConfig, + bool announce) + { + _eventChannel = eventChannel; + _id = id; + FlockId = flockid; + _respawnNoiseFactor = respawnNoiseFactor; + IsLootSplit = lootSplit; + _outpostEID = outpostEID; + _stabilityPts = stabilityPts; + this.overrideRelations = overrideRelations; + _deathMsg = customDeathMsg; + _aggroMsg = customAggroMsg; + _riftConfig = riftConfig; + IsAnnounced = announce; + _speak = true; + IsDead = false; + } + + /// + /// Handle any actions when the boss loses aggro + /// + public void OnDeAggro() + { + _speak = true; + } + + /// + /// Handle any actions that this NPC Boss should do upon Aggression, including sending a message + /// + /// Player aggressor + public void OnAggro(Player aggressor) + { + CommunicateAggression(aggressor); + HandleBossOutpostAggro(aggressor); + } + + + + /// + /// Handle events to dispatch when the npc boss takes damage + /// + /// The npc Boss killed + /// Player damager + public void OnDamageTaken(SmartCreature smartCreature, Player aggressor) + { + if (_onDamageDebounce.Expired) + { + PublishMessage(new NpcReinforcementsMessage(smartCreature, smartCreature.Zone.Id)); + _onDamageDebounce.Reset(); + } + } + + /// + /// Handle any death behavior for this Boss NPC + /// Includes sending a message, and affecting outpost's stability if set + /// + /// The npc Boss killed + /// Player killer + public void OnDeath(Npc npc, Unit killer) + { + CommunicateDeath(killer); + HandleBossOutpostDeath(npc, killer); + SpawnPortal(npc, killer); + IsDead = true; + PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + AnnounceDeath(); + } + + /// + /// The boss lives again + /// + public void OnRespawn() + { + _speak = true; + IsDead = false; + AnnouceRespawn(); + } + + public override bool Equals(object obj) + { + return Equals(obj as NpcBossInfo); + } + + public bool Equals(NpcBossInfo other) + { + return other != null && ReferenceEquals(this, other) || other._id == _id && other.FlockId == FlockId; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 23; + + hash = hash * 31 + _id.GetHashCode(); + hash = hash * 31 + FlockId.GetHashCode(); + + return hash; + } + } + + /// + /// Apply any respawn timer modifiers + /// + /// normal respawn time of npc + /// modified respawn time of npc + public TimeSpan GetNextSpawnTime(TimeSpan respawnTime) + { + var factor = _respawnNoiseFactor ?? 0.0; + + return respawnTime.Multiply(FastRandom.NextDouble(1.0 - factor, 1.0 + factor)); + } + + private void AnnouceRespawn() + { + if (!IsAnnounced) + { + return; + } + + var randomDelay = FastRandom.NextTimeSpan(RespawnTime.Divide(5), RespawnTime.Divide(2)); + var timeStamp = DateTime.UtcNow; + + Task.Delay(randomDelay).ContinueWith((t) => + { + PublishMessage(new NpcStateMessage(FlockId, NpcState.Alive, timeStamp)); + }); + } + + private void AnnounceDeath() + { + if (!IsAnnounced) + { + return; + } + + PublishMessage(new NpcStateMessage(FlockId, NpcState.Dead, DateTime.UtcNow)); + } + + private void HandleBossOutpostAggro(Player aggressor) + { + if (IsOutpostBoss) + { + aggressor.ApplyPvPEffect(); + } + } + + private void CommunicateAggression(Unit aggressor) + { + if (_aggroDebounce.Expired && _speak) + { + _speak = false; + SendMessage(aggressor, _aggroMsg); + _aggroDebounce.Reset(); + } + } + + private void CommunicateDeath(Unit aggressor) + { + SendMessage(aggressor, _deathMsg); + } + + private void HandleBossOutpostDeath(Npc npc, Unit killer) + { + if (!IsOutpostBoss) + { + return; + } + + var zone = npc.Zone; + + IEnumerable outposts = zone.Units.OfType(); + + var outpost = outposts.First(o => o.Eid == _outpostEID); + + if (outpost is Outpost) + { + var participants = npc.ThreatManager.Hostiles + .Select(x => zone.ToPlayerOrGetOwnerPlayer(x.Unit)) + .ToList(); + var builder = StabilityAffectingEvent.Builder() + .WithOutpost(outpost as Outpost) + .WithOverrideRelations(overrideRelations) + .WithSapDefinition(npc.Definition) + .WithSapEntityID(npc.Eid) + .WithPoints(StabilityPoints) + .AddParticipants(participants) + .WithWinnerCorp(zone.ToPlayerOrGetOwnerPlayer(killer).CorporationEid); + + PublishMessage(builder.Build()); + } + } + + private void SpawnPortal(Npc npc, Unit killer) + { + if (!HasRiftToSpawn) + { + return; + } + + PublishMessage(new SpawnPortalMessage(npc.Zone.Id, npc.CurrentPosition, _riftConfig)); + } + + private void SendMessage(Unit src, string msg) + { + if (!msg.IsNullOrEmpty()) + { + PublishMessage(new NpcMessage(msg, src)); + } + } + + private void PublishMessage(IEventMessage eventMessage) + { + _eventChannel.PublishMessage(eventMessage); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 11e90bca9..40ff31c43 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -1,1091 +1,318 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using System.Numerics; -using System.Threading; -using System.Threading.Tasks; -using Perpetuum.Collections; using Perpetuum.Data; using Perpetuum.EntityFramework; -using Perpetuum.ExportedTypes; -using Perpetuum.Items; using Perpetuum.Log; -using Perpetuum.Modules.Weapons; -using Perpetuum.PathFinders; using Perpetuum.Players; using Perpetuum.Services.Looting; using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; -using Perpetuum.StateMachines; -using Perpetuum.Timers; using Perpetuum.Units; -using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Eggs; -using Perpetuum.Zones.Locking; -using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.Movements; -using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.RemoteControl; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; namespace Perpetuum.Zones.NpcSystem { - - public enum NpcBehaviorType - { - Passive, - Neutral, - Aggressive - } - - public abstract class NpcAI : IState + public class Npc : SmartCreature, ITaggable { - protected readonly Npc npc; + private readonly TagHelper tagHelper; - protected NpcAI(Npc npc) + public Npc(TagHelper tagHelper) { - this.npc = npc; + this.tagHelper = tagHelper; } - public virtual void Enter() - { - WriteLog("enter state = " + GetType().Name); - } + public NpcSpecialType SpecialType { get; set; } - public virtual void Exit() - { - npc.StopMoving(); - WriteLog("exit state = " + GetType().Name); - } + public int EP { get; private set; } - public abstract void Update(TimeSpan time); + public ILootGenerator LootGenerator { get; set; } - protected virtual void ToHomeAI() + public void Tag(Player tagger, TimeSpan duration) { - npc.AI.Push(new HomingAI(npc)); + tagHelper.DoTagging(this, tagger, duration); } - protected virtual void ToAggressorAI() + [CanBeNull] + public Player GetTagger() { - if (npc.Behavior.Type == NpcBehaviorType.Passive) - return; - - npc.AI.Push(new AggressorAI(npc)); + return TagHelper.GetTagger(this); } - [Conditional("DEBUG")] - protected void WriteLog(string message) + public override void AcceptVisitor(IEntityVisitor visitor) { -// Logger.InfoFormat("NpcAI: {0}",message); + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } - } - - public class IdleAI : NpcAI - { - private RandomMovement _movement; - public IdleAI(Npc npc) : base(npc) + public override IDictionary GetDebugInfo() { - } + var info = base.GetDebugInfo(); + var homeDistance = HomePosition.TotalDistance2D(CurrentPosition); - public override void Enter() - { - npc.StopAllModules(); - npc.ResetLocks(); - _movement = new RandomMovement(npc.HomePosition, npc.HomeRange); - _movement.Start(npc); - base.Enter(); - } + info.Add("homePositionX", HomePosition.intX); + info.Add("homePositionY", HomePosition.intY); + info.Add("homeRange", HomeRange); + info.Add("homeDistance", homeDistance); + info.Add("coreMax", CoreMax); + info.Add("coreCurrent", Core); + info.Add("bestCombatRange", BestActionRange); - public override void Update(TimeSpan time) - { - if (!npc.IsInHomeRange) - { - ToHomeAI(); - return; - } + var currentAI = AI.Current; - if (!npc.ThreatManager.Hostiles.IsEmpty) + if (currentAI != null) { - ToAggressorAI(); - return; + info.Add("fsm", currentAI.GetType().Name); } - _movement?.Update(npc, time); - } - } - - public class CombatAI : NpcAI - { - private List _moduleActivators; - private readonly IntervalTimer _processHostilesTimer = new IntervalTimer(1500); + info.Add("threat", ThreatManager.ToDebugString()); + Group?.AddDebugInfoToDictionary(info); + info.Add("ismission", GetMissionGuid() != Guid.Empty); - protected CombatAI(Npc npc) : base(npc) - { + return info; } - public override void Enter() + protected override void OnDead(Unit killer) { - _moduleActivators = npc.ActiveModules.Select(m => new ModuleActivator(m)).ToList(); - base.Enter(); - } + var zone = Zone; + var tagger = GetTagger(); - public override void Update(TimeSpan time) - { - _processHostilesTimer.Update(time); - if (_processHostilesTimer.Passed) - { - _processHostilesTimer.Reset(); - ProcessHostiles(); - } - - foreach (var activator in _moduleActivators) - { - activator.Update(time); - } + Debug.Assert(zone != null, "zone != null"); + + BossInfo?.OnDeath(this, killer); + HandleNpcDeadAsync(zone, killer, tagger) + .ContinueWith((t) => base.OnDead(killer)) + .LogExceptions(); } - private bool IsAttackable(Hostile hostile) + public override bool IsWalkable(Vector2 position) { - if (!hostile.unit.InZone) - return false; - - if (hostile.unit.States.Dead) - return false; - - if (!hostile.unit.IsLockable) - return false; - - if (hostile.unit.IsAttackable != ErrorCodes.NoError) - return false; - - if (hostile.unit.IsInvulnerable) - return false; - - if (npc.Behavior.Type == NpcBehaviorType.Neutral) - { - if (hostile.IsExpired) - return false; - } - - var isVisible = npc.IsVisible(hostile.unit); - if (!isVisible) - return false; - - return true; + return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); } - private void ProcessHostiles() + public override bool IsWalkable(Position position) { - foreach (var hostile in npc.ThreatManager.Hostiles) - { - if (!IsAttackable(hostile)) - { - npc.ThreatManager.Remove(hostile); - continue; - } - - if (!npc.IsInLockingRange(hostile.unit)) - continue; - - var mostHated = npc.ThreatManager.GetMostHatedHostile() == hostile; - - var l = npc.GetLockByUnit(hostile.unit); - if (l == null) - { - if (TryMakeFreeLockSlotFor(hostile)) - npc.AddLock(hostile.unit, mostHated); - } - else - { - if (mostHated && !l.Primary) - npc.SetPrimaryLock(l.Id); - } - } + return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); } - private bool TryMakeFreeLockSlotFor(Hostile hostile) + public override bool IsWalkable(int x, int y) { - if (npc.HasFreeLockSlot) - return true; - - var weakestLock = npc.ThreatManager.Hostiles.SkipWhile(h => h != hostile).Skip(1).Select(h => npc.GetLockByUnit(h.unit)).LastOrDefault(); - if (weakestLock == null) - return false; - - weakestLock.Cancel(); - return true; + return Zone.IsWalkableForNpc(x, y, Slope); } - } - public class HomingAI : CombatAI - { - private PathMovement _movement; - - public HomingAI(Npc npc) : base(npc) + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { + SetEP(zone); + + base.OnEnterZone(zone, enterType); } - public override void Enter() + private void SetEP(IZone zone) { - var pathFinder = new AStarFinder(Heuristic.Manhattan, npc.IsWalkable); - pathFinder.FindPathAsync(npc.CurrentPosition, npc.HomePosition).ContinueWith(t => + if (zone.Configuration.Type == ZoneType.Training) { - var path = t.Result; - if (path == null) - { - WriteLog("Path not found! (" + npc.CurrentPosition + " => " + npc.HomePosition + ")"); - - var f = new AStarFinder(Heuristic.Manhattan,(x,y) => true); - path = f.FindPath(npc.CurrentPosition, npc.HomePosition); + EP = 0; + return; + } - if (path == null) - { - WriteLog("Safe path not found! (" + npc.CurrentPosition + " => " + npc.HomePosition + ")"); - } - } + var ep = NpcEp.GetEpForNpc(this); - _movement = new PathMovement(path); - _movement.Start(npc); - }); + if (zone.Configuration.IsBeta) + ep *= 2; - base.Enter(); + EP = ep; + Logger.DebugInfo($"Ep4Npc:{ep} def:{Definition} {ED.Name}"); } - public override void Update(TimeSpan time) + public override string InfoString { - if (_movement != null) + get { - _movement.Update(npc, time); + var infoString = $"Npc:{ED.Name}:{Eid}"; + + var zone = Zone; + if (zone != null) + { + infoString += " z:" + zone.Id; + } - if (_movement.Arrived) + if (Group != null) { - npc.AI.Pop(); - return; + infoString += " g:" + Group.Name; } - } - base.Update(time); + return infoString; + } } - protected override void ToHomeAI() + public override bool IsHostile(Player player) { - // mar haza megy + return true; } - protected override void ToAggressorAI() + internal override bool IsHostile(AreaBomb bomb) { - + return true; } - } - - public class AggressorAI : CombatAI - { - public AggressorAI(Npc npc) : base(npc) + internal override bool IsHostile(SentryTurret turret) { + return true; } - protected override void ToAggressorAI() + internal override bool IsHostile(IndustrialTurret turret) { - // mar combatban van + return true; } - public override void Exit() + internal override bool IsHostile(CombatDrone drone) { - _source?.Cancel(); - - base.Exit(); + return true; } - public override void Update(TimeSpan time) + protected override bool IsHostileFor(Unit unit) { - if (!npc.IsInHomeRange) - { - npc.AI.Push(new HomingAI(npc)); - return; - } + return unit.IsHostile(this); + } - if (npc.ThreatManager.Hostiles.IsEmpty) + protected override void UpdateUnitVisibility(Unit target) + { + if (target is RemoteControlledCreature) { - EnterEvadeMode(); - return; + UpdateVisibility(target); } - - UpdateHostile(time); - - base.Update(time); } - private void EnterEvadeMode() + private Task HandleNpcDeadAsync(IZone zone, Unit killer, Player tagger) { - npc.AI.Pop(); - npc.AI.Push(new HomingAI(npc)); - WriteLog("Enter evade mode."); + return Task.Run(() => HandleNpcDead(zone, killer, tagger)); } - private Position _lastTargetPosition; - private PathMovement _movement; - private PathMovement _nextMovement; - - private void UpdateHostile(TimeSpan time) + private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) { - var mostHated = npc.ThreatManager.GetMostHatedHostile(); - if (mostHated == null) - return; + Logger.DebugInfo($" >>>> NPC died. Killer unitName:{killer.Name} o:{killer.Owner} Tagger botname:{tagger?.Name} o:{killer.Owner} characterId:{tagger?.Character.Id}"); - if (!mostHated.unit.CurrentPosition.IsEqual2D(_lastTargetPosition)) + using (var scope = Db.CreateTransaction()) { - _lastTargetPosition = mostHated.unit.CurrentPosition; + if (BossInfo?.IsLootSplit ?? false) + { + List participants = new List(); + participants = ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.Unit)).ToList(); + + if (participants.Count > 0) + { + ISplittableLootGenerator splitLooter = new SplittableLootGenerator(LootGenerator); + List lootGenerators = splitLooter.GetGenerators(participants.Count); + + for (var i = 0; i < participants.Count; i++) + { + LootContainer.Create() + .SetOwner(participants[i]) + .AddLoot(lootGenerators[i]) + .BuildAndAddToZone(zone, participants[i].CurrentPosition); + } + } + } + else + { + LootContainer.Create().SetOwner(tagger).AddLoot(LootGenerator).BuildAndAddToZone(zone, CurrentPosition); + } - var findNewTargetPosition = false; + var killerPlayer = zone.ToPlayerOrGetOwnerPlayer(killer); - if (!npc.IsInRangeOf3D(mostHated.unit, npc.BestCombatRange)) + if (GetMissionGuid() != Guid.Empty) { - findNewTargetPosition = true; + Logger.DebugInfo(" >>>> NPC is mission related."); + + SearchForMissionOwnerAndSubmitKill(zone, killer); } else { - var visibility = npc.GetVisibility(mostHated.unit); - if (visibility != null) + Logger.DebugInfo(" >>>> independent NPC."); + + if (killerPlayer != null) { - var r = visibility.GetLineOfSight(false); - if (r.hit) - findNewTargetPosition = true; + EnqueueKill(killerPlayer, killer); } } - if (findNewTargetPosition) + if (EP > 0) { - FindNewAttackPositionAsync(mostHated.unit).ContinueWith(t => + var awardedPlayers = new List(); + + foreach (var hostile in ThreatManager.Hostiles.Where(x => x.Unit is Player)) { - if (t.IsCanceled) - return; + var playerUnit = hostile.Unit; + var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); - var path = t.Result; - if (path == null) - { - npc.ThreatManager.Remove(mostHated); - return; - } + hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, EP); + awardedPlayers.Add(playerUnit); + } - Interlocked.Exchange(ref _nextMovement,new PathMovement(path)); - }); + PseudoThreatManager.AwardPseudoThreats(awardedPlayers, zone, EP); } - } - if (_nextMovement != null) - { - _movement = Interlocked.Exchange(ref _nextMovement, null); - _movement.Start(npc); + scope.Complete(); } - - _movement?.Update(npc, time); - } - - private CancellationTokenSource _source; - - private const int SQRT2 = 141; - private const int WEIGHT = 1000; - - private Task> FindNewAttackPositionAsync(Unit hostile) - { - _source?.Cancel(); - - _source = new CancellationTokenSource(); - return Task.Run(() => FindNewAttackPosition(hostile, _source.Token), _source.Token); } - private List FindNewAttackPosition(Unit hostile,CancellationToken cancellationToken) + /// + /// This occurs when aoe kills the npc. + /// Background task that searches for the related missionguid and sumbits the kill for that specific player + /// + private void SearchForMissionOwnerAndSubmitKill(IZone zone, Unit killerUnit) { - var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, npc.BestCombatRange - 1).ToPoint(); - - npc.StopMoving(); - - var maxNode = Math.Pow(npc.HomeRange, 2) * Math.PI; - var pq = new PriorityQueue((int) maxNode); - var startNode = new Node(npc.CurrentPosition); - - pq.Enqueue(startNode); - - var closed = new HashSet(); - closed.Add(startNode.position); + var missionGuid = GetMissionGuid(); + var missionOwner = MissionHelper.FindMissionOwnerByGuid(missionGuid); + var missionOwnerPlayer = zone.GetPlayer(missionOwner); - Node current; - while (pq.TryDequeue(out current)) + if (missionOwnerPlayer == null) { - if (cancellationToken.IsCancellationRequested) - return null; - - if (IsValidAttackPosition(hostile, current.position)) - return BuildPath(current); - - foreach (var n in current.position.GetNeighbours()) + var info = new Dictionary { - if (closed.Contains(n)) - continue; - - closed.Add(n); - - if (!npc.IsWalkable(n.X, n.Y)) - continue; - - if (!n.IsInRange(npc.HomePosition, npc.HomeRange)) - continue; - - var newG = current.g + (n.X - current.position.X == 0 || n.Y - current.position.Y == 0 ? 100 : SQRT2); - var newH = Heuristic.Manhattan.Calculate(n.X, n.Y, end.X, end.Y) * WEIGHT; - - var newNode = new Node(n) - { - g = newG, - f = newG + newH, - parent = current - }; + {k.characterID, missionOwner.Id}, + {k.guid, missionGuid.ToString()}, + {k.type, MissionTargetType.kill_definition}, + {k.definition, ED.Definition}, + {k.increase ,1}, + {k.zoneID, zone.Id}, + {k.position, killerUnit.CurrentPosition}, + }; - pq.Enqueue(newNode); + if (killerUnit is Player killerPlayer && killerPlayer.Character.Id != missionOwner.Id) + { + info[k.assistingCharacterID] = killerPlayer.Character.Id; } - } - - return null; - } - - private bool IsValidAttackPosition(Unit hostile,Point position) - { - var position3 = npc.Zone.FixZ(position.ToPosition()).AddToZ(npc.Height); - - if (!hostile.CurrentPosition.IsInRangeOf3D(position3, npc.BestCombatRange)) - return false; - - var r = npc.Zone.IsInLineOfSight(position3, hostile, false); - return !r.hit; - } - private static List BuildPath(Node current) - { - var stack = new Stack(); + Task.Run(() => + { + MissionHelper.MissionProcessor.NpcGotKilledInAway(missionOwner, missionGuid, info); + }); - var node = current; - while (node != null) - { - stack.Push(node.position); - node = node.parent; + return; } - return stack.ToList(); + EnqueueKill(missionOwnerPlayer, killerUnit); } - private class Node : IComparable + private void EnqueueKill(Player missionOwnerPlayer, Unit killerUnit) { - public readonly Point position; - public Node parent; - public int g; - public int f; - - public Node(Point position) - { - this.position = position; - } - - public int CompareTo(Node other) - { - return f - other.f; - } + var eventSourcePlayer = missionOwnerPlayer; - public override int GetHashCode() + if (killerUnit is Player killerPlayer && !killerPlayer.Equals(missionOwnerPlayer)) { - return position.GetHashCode(); + eventSourcePlayer = killerPlayer; } - } - } - - public class NpcBehavior - { - public NpcBehaviorType Type { get; private set; } - - protected NpcBehavior(NpcBehaviorType type) - { - Type = type; - } - public void Update(TimeSpan time) - { - - } + Logger.DebugInfo($" >>>> EventSource: botName:{eventSourcePlayer.Name} o:{eventSourcePlayer.Owner} characterId:{eventSourcePlayer.Character.Id} MissionOwner: botName:{missionOwnerPlayer.Name} o:{missionOwnerPlayer.Owner} characterId:{missionOwnerPlayer.Character.Id}"); - public static NpcBehavior Create(NpcBehaviorType type) - { - switch (type) - { - case NpcBehaviorType.Neutral: - return new NeutralBehavior(); - case NpcBehaviorType.Aggressive: - return new AggressiveBehavior(); - - case NpcBehaviorType.Passive: - return new PassiveBehavior(); - default: - return new PassiveBehavior(); - } + missionOwnerPlayer.MissionHandler.EnqueueMissionEventInfoLocally(new KillEventInfo(eventSourcePlayer, this, CurrentPosition)); } } - - public class PassiveBehavior : NpcBehavior - { - public PassiveBehavior() : base(NpcBehaviorType.Passive) - { - } - } - - public class NeutralBehavior : NpcBehavior - { - public NeutralBehavior() : base(NpcBehaviorType.Neutral) - { - } - } - - public class AggressiveBehavior : NpcBehavior - { - public AggressiveBehavior() : base(NpcBehaviorType.Aggressive) - { - } - } - - public class Npc : Creature,ITaggable - { - private readonly TagHelper _tagHelper; - private const double CALL_FOR_HELP_ARMOR_THRESHOLD = 0.2; - private readonly ThreatManager _threatManager; - private object _bestCombatRange; - private TimeSpan _lastHelpCalled; - - public Npc(TagHelper tagHelper) - { - _tagHelper = tagHelper; - _threatManager = new ThreatManager(); - AI = new StackFSM(); - } - - public NpcBehavior Behavior { get; set; } - - [CanBeNull] - private INpcGroup _group; - - public void SetGroup(INpcGroup group) - { - _group = group; - } - - public INpcGroup Group - { - get { return _group; } - } - - public StackFSM AI { get; private set; } - - public IThreatManager ThreatManager - { - get { return _threatManager; } - } - - public ILootGenerator LootGenerator { get; set; } - - public double HomeRange { get; set; } - public Position HomePosition { get; set; } - - public bool IsInHomeRange - { - get { return CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); } - } - - public int BestCombatRange - { - get { return (int) (_bestCombatRange ?? (_bestCombatRange = CalculateCombatRange())); } - } - - public void Tag(Player tagger,TimeSpan duration) - { - _tagHelper.DoTagging(this,tagger,duration); - } - - [CanBeNull] - public Player GetTagger() - { - return TagHelper.GetTagger(this); - } - - public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) - { - _threatManager.GetHostile(hostile).AddThreat(threat); - - if (!spreadToGroup) - return; - - var group = _group; - if (@group == null) - return; - - var t = Threat.Multiply(threat, 0.5); - - foreach (var member in @group.Members) - { - if (member == this) - continue; - member.AddThreat(hostile,t,false); - } - } - - public override void AcceptVisitor(IEntityVisitor visitor) - { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); - } - - protected override void OnPropertyChanged(ItemProperty property) - { - base.OnPropertyChanged(property); - - switch (property.Field) - { - case AggregateField.locking_range: - { - _bestCombatRange = null; - break; - } - case AggregateField.armor_current: - { - var percentage = Armor.Ratio(ArmorMax); - if (percentage <= CALL_FOR_HELP_ARMOR_THRESHOLD) - { - CallingForHelp(); - } - - break; - } - } - } - - public override IDictionary GetDebugInfo() - { - var info = base.GetDebugInfo(); - var homeDistance = HomePosition.TotalDistance2D(CurrentPosition); - - info.Add("homePositionX", HomePosition.intX); - info.Add("homePositionY", HomePosition.intY); - info.Add("homeRange", HomeRange); - info.Add("homeDistance", homeDistance); - info.Add("coreMax", CoreMax); - info.Add("coreCurrent", Core); - info.Add("bestCombatRange", BestCombatRange); - - var currentAI = AI.Current; - if (currentAI != null) - info.Add("fsm", currentAI.GetType().Name); - - info.Add("threat", _threatManager.ToDebugString()); - - _group?.AddDebugInfoToDictionary(info); - - info.Add("ismission",GetMissionGuid() != Guid.Empty); - - return info; - } - - protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) - { - base.OnDamageTaken(source, e); - - var player = Zone.ToPlayerOrGetOwnerPlayer(source); - if (player == null) - return; - - AddThreat(player,new Threat(ThreatType.Damage, e.TotalDamage * 0.9),true); - } - - protected override void OnDead(Unit killer) - { - var zone = Zone; - var tagger = GetTagger(); - Debug.Assert(zone != null, "zone != null"); - - HandleNpcDeadAsync(zone, killer, tagger).ContinueWith((t) => base.OnDead(killer)).LogExceptions(); - } - - private Task HandleNpcDeadAsync(IZone zone, Unit killer, Player tagger) - { - return Task.Run(() => HandleNpcDead(zone, killer, tagger)); - } - - private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) - { - Logger.DebugInfo($" >>>> NPC died. Killer unitName:{killer.Name} o:{killer.Owner} Tagger botname:{tagger?.Name} o:{killer.Owner} characterId:{tagger?.Character.Id}"); - - using (var scope = Db.CreateTransaction()) - { - LootContainer.Create().SetOwner(tagger).AddLoot(LootGenerator).BuildAndAddToZone(zone, CurrentPosition); - - var killerPlayer = zone.ToPlayerOrGetOwnerPlayer(killer); - - if (GetMissionGuid() != Guid.Empty) - { - Logger.DebugInfo(" >>>> NPC is mission related."); - - SearchForMissionOwnerAndSubmitKill(zone, killer); - } - else - { - Logger.DebugInfo(" >>>> independent NPC."); - - if (killerPlayer != null) - EnqueueKill(killerPlayer, killer); - } - - var ep = Db.Query().CommandText("GetNpcKillEp").SetParameter("@definition", Definition).ExecuteScalar(); - - //Logger.Warning($"Ep4Npc:{ep} def:{Definition} {ED.Name}"); - - if (zone.Configuration.IsBeta) - ep *= 2; - - if (zone.Configuration.Type == ZoneType.Training) ep = 0; - - if (ep > 0) - { - foreach (var hostile in ThreatManager.Hostiles) - { - var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(hostile.unit); - hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep); - } - } - - scope.Complete(); - } - } - - - /// - /// This occurs when aoe kills the npc. - /// Background task that searches for the related missionguid and sumbits the kill for that specific player - /// - private void SearchForMissionOwnerAndSubmitKill(IZone zone, Unit killerUnit) - { - var missionGuid = GetMissionGuid(); - var missionOwner = MissionHelper.FindMissionOwnerByGuid(missionGuid); - - var missionOwnerPlayer = zone.GetPlayer(missionOwner); - if (missionOwnerPlayer == null) - { - //the owner is not this zone - //address the mission plugin directly - - var info = new Dictionary - { - {k.characterID, missionOwner.Id}, - {k.guid, missionGuid.ToString()}, - {k.type, MissionTargetType.kill_definition}, - {k.definition, ED.Definition}, - {k.increase ,1}, - {k.zoneID, zone.Id}, - {k.position, killerUnit.CurrentPosition} - }; - - if (killerUnit is Player killerPlayer && killerPlayer.Character.Id != missionOwner.Id) - { - info[k.assistingCharacterID] = killerPlayer.Character.Id; - } - - Task.Run(() => - { - MissionHelper.MissionProcessor.NpcGotKilledInAway(missionOwner,missionGuid,info); - }); - return; - } - - //local enqueue, this is the proper player, we can skip gang - EnqueueKill(missionOwnerPlayer, killerUnit); - } - - private void EnqueueKill(Player missionOwnerPlayer, Unit killerUnit) - { - var eventSourcePlayer = missionOwnerPlayer; - var killerPlayer = killerUnit as Player; - if (killerPlayer != null && !killerPlayer.Equals(missionOwnerPlayer)) - { - eventSourcePlayer = killerPlayer; - } - - Logger.DebugInfo($" >>>> EventSource: botName:{eventSourcePlayer.Name} o:{eventSourcePlayer.Owner} characterId:{eventSourcePlayer.Character.Id} MissionOwner: botName:{missionOwnerPlayer.Name} o:{missionOwnerPlayer.Owner} characterId:{missionOwnerPlayer.Character.Id}"); - - //local enqueue, this is the proper player, we can skip gang - missionOwnerPlayer.MissionHandler.EnqueueMissionEventInfoLocally(new KillEventInfo(eventSourcePlayer, this, CurrentPosition)); - } - - protected override void OnTileChanged() - { - base.OnTileChanged(); - LookingForHostiles(); - } - - private void LookingForHostiles() - { - foreach (var visibility in GetVisibleUnits()) - { - AddBodyPullThreat(visibility.Target); - } - } - - protected override void OnUpdate(TimeSpan time) - { - base.OnUpdate(time); - - Behavior.Update(time); - _threatManager.Update(); - - AI.Update(time); - } - - protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) - { - States.Aggressive = Behavior.Type == NpcBehaviorType.Aggressive; - - base.OnEnterZone(zone, enterType); - - AI.Push(new IdleAI(this)); - } - - public override string InfoString - { - get - { - var s = $"Npc:{ED.Name}:{Eid}"; - - var zone = Zone; - if (zone != null) - { - s += " z:" + zone.Id; - } - - if (_group != null) - s += " g:" + _group.Name; - - return s; - } - } - - private const double AGGRO_RANGE = 30; - - private bool IsInAggroRange(Unit target) - { - return IsInRangeOf3D(target, AGGRO_RANGE); - } - - protected override void OnUnitLockStateChanged(Lock @lock) - { - var unitLock = @lock as UnitLock; - if ( unitLock == null ) - return; - - if (unitLock.Target != this) - return; - - if (unitLock.State != LockState.Locked) - return; - - var threatValue = unitLock.Primary ? Threat.LOCK_PRIMARY : Threat.LOCK_SECONDARY; - AddThreat(unitLock.Owner,new Threat(ThreatType.Lock, threatValue),true); - } - - protected override void OnUnitTileChanged(Unit target) - { - AddBodyPullThreat(target); - } - - internal override bool IsHostile(Player player) - { - return true; - } - - internal override bool IsHostile(AreaBomb bomb) - { - return true; - } - - public bool CanAddThreatTo(Unit target,Threat threat) - { - var hasThreat = _threatManager.Contains(target); - if (hasThreat) - return true; - - var check = false; - - switch (Behavior.Type) - { - case NpcBehaviorType.Passive: - return false; - case NpcBehaviorType.Neutral: - { - switch (threat.type) - { - case ThreatType.Direct: - { - // itt nincs aggrorange,mehet mindig - return true; - } - - case ThreatType.Debuff: - case ThreatType.Support: - case ThreatType.Lock: - { - check = true; - break; - } - } - break; - } - case NpcBehaviorType.Aggressive: - { - check = true; - break; - } - } - - return check && IsInAggroRange(target); - } - - private void AddBodyPullThreat(Unit enemy) - { - if ( !IsHostile(enemy)) - return; - - var helper = new BodyPullThreatHelper(this); - enemy.AcceptVisitor(helper); - } - - public bool CallForHelp { private get; set; } - - private void CallingForHelp() - { - if (!CallForHelp) - return; - - if (!GlobalTimer.IsPassed(ref _lastHelpCalled, TimeSpan.FromSeconds(5))) - return; - - var group = _group; - if (group == null) - return; - - foreach (var member in group.Members.Where(flockMember => flockMember != this)) - { - member.HelpingFor(this); - } - } - - private void HelpingFor(Npc caller) - { - if (Armor.Ratio(ArmorMax) < CALL_FOR_HELP_ARMOR_THRESHOLD) - return; - - _threatManager.Clear(); - foreach (var hostile in caller.ThreatManager.Hostiles) - { - AddThreat(hostile.unit,new Threat(ThreatType.Undefined,hostile.Threat),true); - } - } - - public void AddAssistThreat(Unit assistant, Unit target, Threat threat) - { - if ( !_threatManager.Contains(target) ) - return; - - if ( !CanAddThreatTo(assistant,threat)) - return; - - AddThreat(assistant,threat,true); - } - - private int CalculateCombatRange() - { - double range = (int)ActiveModules.Where(m => m.IsRanged) - .Select(module => module.OptimalRange) - .Concat(new[] { MaxTargetingRange }) - .Min(); - - range *= BEST_COMBAT_RANGE_MODIFIER; - - return (int) range; - } - - private const double BEST_COMBAT_RANGE_MODIFIER = 0.5; - - private class BodyPullThreatHelper : IEntityVisitor,IEntityVisitor - { - private readonly Npc _npc; - - public BodyPullThreatHelper(Npc npc) - { - _npc = npc; - } - - public void Visit(Player player) - { - if (_npc.Behavior.Type != NpcBehaviorType.Aggressive) - return; - - if (player.HasTeleportSicknessEffect) - return; - - if (!_npc.ThreatManager.Hostiles.IsEmpty) - return; - - if (!_npc.IsInAggroRange(player)) - return; - - var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); - _npc.AddThreat(player, new Threat(ThreatType.Bodypull, threat)); - } - - public void Visit(AreaBomb bomb) - { - if (!_npc.IsInAggroRange(bomb)) - return; - - // csak akkor ha van is mivel tamadni - if (!_npc.ActiveModules.Any(m => m is WeaponModule)) - return; - - // ha valaki mar foglalkozik a bombaval akkor ne csinaljon semmit - - var g = _npc._group; - if (g != null && g.Members.Any(m => m.ThreatManager.Contains(bomb))) - return; - - var threat = Threat.BODY_PULL; - if (!_npc.ThreatManager.Hostiles.IsEmpty) - { - var h = _npc.ThreatManager.GetMostHatedHostile(); - if (h != null) - threat = h.Threat*100; - } - - _npc.AddThreat(bomb, new Threat(ThreatType.Bodypull, threat + FastRandom.NextDouble(0, 5))); - } - } - - public override bool IsWalkable(Vector2 position) - { - return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); - } - - public override bool IsWalkable(Position position) - { - return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); - } - - public override bool IsWalkable(int x, int y) - { - return Zone.IsWalkableForNpc(x, y, Slope); - } - - } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/NpcBossInfoBuilder.cs b/src/Perpetuum/Zones/NpcSystem/NpcBossInfoBuilder.cs new file mode 100644 index 000000000..72ad96fd4 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/NpcBossInfoBuilder.cs @@ -0,0 +1,76 @@ +using Perpetuum.Data; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Zones.NpcSystem.Flocks; +using System.Data; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem +{ + public class NpcBossInfoBuilder + { + private readonly ICustomRiftConfigReader _customRiftConfigReader; + private readonly EventListenerService _eventChannel; + + public NpcBossInfoBuilder(ICustomRiftConfigReader customRiftConfigReader, EventListenerService eventChannel) + { + _customRiftConfigReader = customRiftConfigReader; + _eventChannel = eventChannel; + } + + public NpcBossInfo CreateBossInfoFromDB(IDataRecord record) + { + var id = record.GetValue("id"); + var flockid = record.GetValue("flockid"); + var respawnFactor = record.GetValue("respawnNoiseFactor"); + var lootSplit = record.GetValue("lootSplitFlag"); + var outpostEID = record.GetValue("outpostEID"); + var stabilityPts = record.GetValue("stabilityPts"); + var overrideRelations = record.GetValue("overrideRelations"); + var deathMessage = record.GetValue("customDeathMessage"); + var aggressMessage = record.GetValue("customAggressMessage"); + var riftConfigId = record.GetValue("riftConfigId"); + var riftConfig = _customRiftConfigReader.GetById(riftConfigId ?? -1); + var announce = record.GetValue("isAnnounced"); + var info = new NpcBossInfo( + _eventChannel, + id, + flockid, + respawnFactor, + lootSplit, + outpostEID, + stabilityPts, + overrideRelations, + deathMessage, + aggressMessage, + riftConfig, + announce + ); + + return info; + } + + public NpcBossInfo GetBossInfoByFlockID(int flockid, IFlockConfiguration config) + { + var bossInfos = Db.Query() + .CommandText(@"SELECT TOP 1 id, flockid, respawnNoiseFactor, lootSplitFlag, outpostEID, + stabilityPts, overrideRelations, customDeathMessage, customAggressMessage, riftConfigId, + isAnnounced + FROM dbo.npcbossinfo WHERE flockid=@flockid;") + .SetParameter("@flockid", flockid) + .Execute() + .Select(CreateBossInfoFromDB); + + var info = bossInfos.SingleOrDefault(); + + if (info == null) + { + return null; + } + + info.RespawnTime = config.RespawnTime; + + return info; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/NpcEp.cs b/src/Perpetuum/Zones/NpcSystem/NpcEp.cs new file mode 100644 index 000000000..edcdadfec --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/NpcEp.cs @@ -0,0 +1,19 @@ +using Perpetuum.Data; +using System.Collections.Concurrent; + +namespace Perpetuum.Zones.NpcSystem +{ + public static class NpcEp + { + private static readonly ConcurrentDictionary _npcEp = new ConcurrentDictionary(); + + public static int GetEpForNpc(Npc npc) + { + var definition = npc.Definition; + if (definition <= 0) + return 0; + + return _npcEp.GetOrAdd(definition, (d) => Db.Query().CommandText("GetNpcKillEp").SetParameter("@definition", d).ExecuteScalar()); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/NpcSpecialType.cs b/src/Perpetuum/Zones/NpcSystem/NpcSpecialType.cs new file mode 100644 index 000000000..808935d19 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/NpcSpecialType.cs @@ -0,0 +1,8 @@ +namespace Perpetuum.Zones.NpcSystem +{ + public enum NpcSpecialType + { + Normal, + Boss + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DirectPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DirectPresence.cs index aed70ea9b..141825e9c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DirectPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DirectPresence.cs @@ -11,7 +11,7 @@ public sealed class DirectPresence : DynamicPresence private readonly IRobotTemplateRelations _robotTemplateRelations; private readonly FlockConfigurationBuilder.Factory _flockConfigurationBuilderFactory; - public DirectPresence(IZone zone, PresenceConfiguration configuration, + public DirectPresence(IZone zone, IPresenceConfiguration configuration, IRobotTemplateRelations robotTemplateRelations, FlockConfigurationBuilder.Factory flockConfigurationBuilderFactory) : base(zone, configuration) { diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs index 4caf4b7df..03a34bdec 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs @@ -20,7 +20,7 @@ public sealed class DynamicPoolPresence : DynamicPresence private readonly StackFSM _fsm = new StackFSM(); public Character Summoner { private get; set; } - public DynamicPoolPresence(IZone zone, PresenceConfiguration configuration,IRandomFlockReader randomFlockReader) : base(zone, configuration) + public DynamicPoolPresence(IZone zone, IPresenceConfiguration configuration,IRandomFlockReader randomFlockReader) : base(zone, configuration) { _randomFlockReader = randomFlockReader; } @@ -43,7 +43,7 @@ public void Init(int totalWaves) for (var i = 0; i < totalWaves; i++) { - _fsm.Push(CreateWaveState(normalFlocks.RandomElement(Configuration.maxRandomFlock), false)); + _fsm.Push(CreateWaveState(normalFlocks.RandomElement(Configuration.MaxRandomFlock), false)); } } @@ -57,7 +57,7 @@ private IState CreatePortalState() first = false; - var portal = (Portal)Unit.CreateUnitWithRandomEID(DefinitionNames.RANDOM_RIFT_PORTAL); + var portal = (DespawningPortal)Unit.CreateUnitWithRandomEID(DefinitionNames.RANDOM_RIFT_PORTAL); portal.SetDespawnTime(TimeSpan.FromMinutes(5)); portal.AddToZone(Zone, DynamicPosition); }); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs index 39a1105d5..0842cf6af 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs @@ -5,11 +5,9 @@ namespace Perpetuum.Zones.NpcSystem.Presences { - public class DynamicPresence : Presence,INotifyPresenceExpired + public class ExpiringPresence : Presence { - private TimeTracker _lifeTimeTracker = new TimeTracker(TimeSpan.FromHours(1)); - - public Position DynamicPosition { get; set; } + protected TimeTracker _lifeTimeTracker = new TimeTracker(TimeSpan.FromHours(1)); public TimeSpan LifeTime { @@ -17,17 +15,10 @@ public TimeSpan LifeTime set { _lifeTimeTracker = new TimeTracker(value); } } - public event Action PresenceExpired; - - public DynamicPresence(IZone zone,PresenceConfiguration configuration) : base(zone,configuration) - { - if (Configuration.dynamicLifeTime != null) - LifeTime = TimeSpan.FromMilliseconds((int) Configuration.dynamicLifeTime); - } - - public override Area Area + public ExpiringPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { - get { return Zone.Size.ToArea(); } + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); } public void ResetDynamicDespawnTimer() @@ -37,7 +28,7 @@ public void ResetDynamicDespawnTimer() protected override void OnUpdate(TimeSpan time) { - var x = Flocks.GetMembers().Any(m => m.AI.Current is AggressorAI); + var x = Flocks.GetMembers().Any(m => m.ThreatManager.IsThreatened); if (x) ResetDynamicDespawnTimer(); @@ -49,11 +40,74 @@ protected override void OnUpdate(TimeSpan time) } } - protected virtual void OnPresenceExpired() + protected virtual void OnPresenceExpired() { } + } + + public class DynamicPresence : ExpiringPresence, INotifyPresenceExpired + { + public Position DynamicPosition { get; set; } + + public event Action PresenceExpired; + + public DynamicPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { - ClearFlocks(); + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromMilliseconds((int)Configuration.DynamicLifeTime); + } + + public override Area Area + { + get { return Zone.Size.ToArea(); } + } + protected override void OnPresenceExpired() + { + base.OnPresenceExpired(); + ClearFlocks(); PresenceExpired?.Invoke(this); } } + + /// + /// This is a Dynamic Presence that fires the expires event when its lifetime is up OR if all flocks have been killed + /// It also has a customizable SpawnLocation that is separate from its home-point or origin. + /// + public class DynamicPresenceExtended : DynamicPresence + { + public Position SpawnLocation { get; set; } + + public DynamicPresenceExtended(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) + { + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); + } + + protected override void OnFlockAdded(Flock flock) + { + flock.AllMembersDead += OnFlockAllMembersDead; + base.OnFlockAdded(flock); + } + + private void OnFlockAllMembersDead(Flock flock) + { + flock.AllMembersDead -= OnFlockAllMembersDead; + RemoveFlock(flock); + OnFlockRemoved(); + } + + protected override void OnPresenceExpired() + { + foreach (var flock in Flocks) + { + flock.AllMembersDead -= OnFlockAllMembersDead; + } + base.OnPresenceExpired(); + } + + private void OnFlockRemoved() + { + if (Flocks.IsNullOrEmpty()) + OnPresenceExpired(); + } + } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/FlockEscalationSelector.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/FlockEscalationSelector.cs new file mode 100644 index 000000000..4523ab284 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/FlockEscalationSelector.cs @@ -0,0 +1,85 @@ +using Perpetuum.Data; +using Perpetuum.Zones.NpcSystem.Flocks; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence +{ + public interface IEscalatingPresenceFlockSelector + { + IFlockConfiguration[] GetFlocksForPresenceLevel(int presenceId, int level); + int GetMaxLevelForPresence(int presenceId); + } + public class EscalatingPresenceFlockSelector : IEscalatingPresenceFlockSelector + { + private readonly IFlockConfigurationRepository _flockConfigurationRepository; + private readonly IEscalatingFlocksReader _reader; + private readonly Random _random; + public EscalatingPresenceFlockSelector(IEscalatingFlocksReader reader, IFlockConfigurationRepository flockConfigurationRepository) + { + _flockConfigurationRepository = flockConfigurationRepository; + _reader = reader; + _random = new Random(); + } + public IFlockConfiguration[] GetFlocksForPresenceLevel(int presenceId, int level) + { + var infos = _reader.GetByPresence(presenceId).Where(info => info.Level == level); + var flocks = new List(); + foreach (var info in infos) + { + if (info.Chance >= _random.NextDouble()) + { + flocks.Add(_flockConfigurationRepository.Get(info.FlockId)); + } + } + return flocks.ToArray(); + } + + public int GetMaxLevelForPresence(int presenceId) + { + return _reader.GetByPresence(presenceId).Select(info=>info.Level).DefaultIfEmpty(0).Max(); + } + } + + public class EscalationInfo + { + public int FlockId { get; set; } + public int Level { get; set; } + public double Chance { get; set; } + } + + public interface IEscalatingFlocksReader + { + EscalationInfo[] GetByPresence(int presenceId); + } + + public class EscalatingFlocksReader : IEscalatingFlocksReader + { + private ILookup _flockInfos; + + public void Init() + { + _flockInfos = Db.Query().CommandText("select * from npcescalactions") + .Execute() + .Select(r => + { + return new + { + presenceID = r.GetValue("presenceid"), + info = new EscalationInfo + { + FlockId = r.GetValue("flockid"), + Level = r.GetValue("level"), + Chance = r.GetValue("chance") + } + }; + }).ToLookup(x => x.presenceID, x => x.info); + } + + public EscalationInfo[] GetByPresence(int presenceId) + { + return _flockInfos.GetOrEmpty(presenceId); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/IRandomStaticPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/IRandomStaticPresence.cs new file mode 100644 index 000000000..cd33487a0 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/IRandomStaticPresence.cs @@ -0,0 +1,4 @@ +namespace Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence +{ + interface IRandomStaticPresence : IRoamingPresence { } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs new file mode 100644 index 000000000..d8dbd10fa --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs @@ -0,0 +1,57 @@ +using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using System; +using System.Drawing; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; + +namespace Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence +{ + /// + /// A non-roaming ExpiringPresence that would spawning with Roaming rules + /// + public class RandomSpawningExpiringPresence : ExpiringPresence, IRandomStaticPresence, IRoamingPresence + { + public StackFSM StackFSM { get; protected set; } + public Position SpawnOrigin { get; set; } + public IRoamingPathFinder PathFinder { get; set; } + public override Area Area => Configuration.Area; + public Point CurrentRoamingPosition { get; set; } + + public RandomSpawningExpiringPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) + { + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); + + InitStateMachine(); + } + + protected virtual void InitStateMachine() + { + StackFSM = new StackFSM(); + StackFSM.Push(new StaticSpawnState(this)); + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + StackFSM?.Update(time); + } + + protected override void OnPresenceExpired() + { + StackFSM?.Clear(); + foreach (var flock in Flocks) + { + flock.RemoveAllMembersFromZone(true); + } + ResetDynamicDespawnTimer(); + InitStateMachine(); + base.OnPresenceExpired(); + } + + public virtual void OnSpawned() + { + ResetDynamicDespawnTimer(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs new file mode 100644 index 000000000..a6fc056e3 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs @@ -0,0 +1,28 @@ +using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; + +namespace Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence +{ + public class StaticExpiringFlock : RoamingFlock + { + public StaticExpiringFlock(IFlockConfiguration configuration, Presence presence) : base(configuration, presence) { } + + protected override bool IsPresenceInSpawningState() + { + if (Presence is IRandomStaticPresence pres) + { + return pres.StackFSM.Current is StaticSpawnState; + } + return base.IsPresenceInSpawningState(); + } + + protected override Position GetSpawnPosition(Position spawnOrigin) + { + if (Presence is IRandomStaticPresence pres) + { + spawnOrigin = pres.SpawnOrigin.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticSpawnState.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticSpawnState.cs new file mode 100644 index 000000000..e0c9efe21 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticSpawnState.cs @@ -0,0 +1,41 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using Perpetuum.Zones.Teleporting; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence +{ + public class StaticSpawnState : SpawnState + { + private readonly int _baseRadius = 300; + private readonly int _teleRadius = 150; + public StaticSpawnState(IRoamingPresence presence, int playerMinDist = 200, int baseDist = 300, int teleportDist = 150) : base(presence, playerMinDist) + { + _baseRadius = baseDist; + _teleRadius = teleportDist; + } + + protected override void OnSpawned() + { + _presence.OnSpawned(); + _presence.StackFSM.Push(new NullRoamingState(_presence)); + } + + protected override bool IsInRange(Position position, int range) + { + var zone = _presence.Zone; + if (zone.Configuration.IsGamma && zone.IsUnitWithCategoryInRange(CategoryFlags.cf_pbs_docking_base, position, _baseRadius)) + return true; + else if (zone.GetStaticUnits().OfType().WithinRange2D(position, _baseRadius).Any()) + return true; + else if (zone.GetStaticUnits().OfType().WithinRange2D(position, _teleRadius).Any()) + return true; + else if (zone.PresenceManager.GetPresences().OfType().Where(p => p.SpawnOrigin.IsInRangeOf2D(position, _baseRadius)).Any()) + return true; + + return base.IsInRange(position, range); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingNPCBasePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingNPCBasePresence.cs new file mode 100644 index 000000000..9c98d8038 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingNPCBasePresence.cs @@ -0,0 +1,29 @@ +using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Presences.GrowingPresences +{ + public class GrowingNPCBasePresence : GrowingPresence + { + public GrowingNPCBasePresence(IZone zone, IPresenceConfiguration configuration, IEscalatingPresenceFlockSelector selector) : base(zone, configuration, selector) { } + protected override void InitStateMachine() + { + CurrentGrowthLevel = FastRandom.NextInt(9); + StackFSM = new StackFSM(); + StackFSM.Push(new NPCBaseSpawnState(this)); + } + } + + public static class NPCBasePresenceUtils + { + public static bool WithinRangeOfNPCBase(IZone zone, Position from, double range = DistanceConstants.PBS_DIST_FROM_NPC_BASE) + { + return zone.PresenceManager.GetPresences() + .OfType() + .Where(p => p.Members.Count() > 0) + .Select(p => p.SpawnOrigin) + .Any(pos => pos.TotalDistance2D(from) < range); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingPresence.cs new file mode 100644 index 000000000..2cdca1943 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingPresence.cs @@ -0,0 +1,57 @@ +using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; +using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; +using System; + +namespace Perpetuum.Zones.NpcSystem.Presences.GrowingPresences +{ + public interface IGrowingPresence + { + TimeSpan GrowTime { get; } + IEscalatingPresenceFlockSelector Selector { get; } + int CurrentGrowthLevel { get; } + } + public class GrowingPresence : RandomSpawningExpiringPresence, IGrowingPresence + { + public TimeSpan GrowTime { get; private set; } + public virtual IEscalatingPresenceFlockSelector Selector { get; protected set; } + public int CurrentGrowthLevel { get; protected set; } + public GrowingPresence(IZone zone, IPresenceConfiguration configuration, IEscalatingPresenceFlockSelector selector) : base(zone, configuration) + { + Selector = selector; + if (Configuration.GrowthSeconds != null) + GrowTime = TimeSpan.FromSeconds((int)Configuration.GrowthSeconds); + } + + protected override void InitStateMachine() + { + StackFSM = new StackFSM(); + StackFSM.Push(new GrowSpawnState(this)); + } + + public override void LoadFlocks() + { + for (var i = 0; i <= CurrentGrowthLevel; i++) + { + var flockConfigs = Selector.GetFlocksForPresenceLevel(ID, i); + foreach (var config in flockConfigs) + { + CreateAndAddFlock(config); + } + } + } + + public void OnWaveSpawn(int level) + { + CurrentGrowthLevel = Math.Max(CurrentGrowthLevel, level); + } + + protected override void OnPresenceExpired() + { + base.OnPresenceExpired(); + ClearFlocks(); + CurrentGrowthLevel = 0; + LoadFlocks(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowthStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowthStates.cs new file mode 100644 index 000000000..7551f9f23 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowthStates.cs @@ -0,0 +1,156 @@ +using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using System; + +namespace Perpetuum.Zones.NpcSystem.Presences.GrowingPresences +{ + public class GrowSpawnState : StaticSpawnState + { + private readonly GrowingPresence _growingPresence; + public GrowSpawnState(GrowingPresence presence) : base(presence) + { + _growingPresence = presence; + } + + protected override void OnSpawned() + { + _presence.OnSpawned(); + _presence.StackFSM.Push(new GrowthState(_growingPresence)); + } + } + + public class NPCBaseSpawnState : GrowSpawnState + { + private readonly GrowingNPCBasePresence _growingPresence; + public NPCBaseSpawnState(GrowingNPCBasePresence presence) : base(presence) + { + _growingPresence = presence; + } + + protected override void OnSpawned() + { + _presence.OnSpawned(); + _presence.StackFSM.Push(new NPCBaseGrowthState(_growingPresence)); + } + + protected override void SetSpawnDelay() + { + if (_growingPresence.CurrentGrowthLevel == 0) + { + _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); + } + _delay = TimeSpan.FromSeconds((int)_presence.Configuration.DynamicLifeTime * _repawnDelayModifier); + _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); + } + + protected override bool IsValidSpawnPosition(Position position, int range) + { + if (!IsLocalRadiusClearForBase(position)) + return false; + + return !IsInRange(position, range); + } + private bool IsLocalRadiusClearForBase(Position center, int radius = 5) + { + var zone = _presence.Zone; + if (zone == null) + return false; + + int minX = (center.intX - radius).Clamp(0, zone.Size.Width - 1); + int maxX = (center.intX + radius).Clamp(0, zone.Size.Width - 1); + int minY = (center.intY - radius).Clamp(0, zone.Size.Height - 1); + int maxY = (center.intY + radius).Clamp(0, zone.Size.Height - 1); + + var check = false; + for (var j = minY; j < maxY; j++) + { + for (var i = minX; i < maxX; i++) + { + var cPos = new Position(i, j); + if (!zone.Size.Contains(cPos.intX, cPos.intY)) + continue; + + var controlInfo = zone.Terrain.Controls.GetValue(cPos.intX, cPos.intY); + if (controlInfo.IsAnyTerraformProtected || !zone.Terrain.Slope.CheckSlope(cPos.intX, cPos.intY, ZoneExtensions.MIN_SLOPE)) + { + return false; + } + check = true; + } + } + return check; + } + + protected override Position FindSpawnPosition() + { + return _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); + } + } + + public class NPCBaseGrowthState : GrowthState + { + public NPCBaseGrowthState(GrowingNPCBasePresence presence) : base(presence) + { + _currentLevel = presence.CurrentGrowthLevel; + } + } + + public class GrowthState : NullRoamingState + { + private readonly GrowingPresence _growingPresence; + private readonly TimeTracker _timer; + protected int _currentLevel = 0; + public GrowthState(GrowingPresence presence) : base(presence) + { + _growingPresence = presence; + _timer = new TimeTracker(_growingPresence.GrowTime); + _currentLevel = _growingPresence.CurrentGrowthLevel; + } + + public override void Update(TimeSpan time) + { + if (IsRunningTask) + return; + + var members = GetAllMembers(); + if (IsDeadAndExiting(members)) + return; + + if (!NextWaveReady(time)) + return; + + RunTask(() => SpawnNextWave(), t => { }); + } + + private bool NextWaveReady(TimeSpan time) + { + if (!CheckTimer(time)) + return false; + + _currentLevel++; + return true; + } + + private bool CheckTimer(TimeSpan time) + { + _timer.Update(time); + if (!_timer.Expired) + return false; + + _timer.Reset(); + return true; + } + + private void SpawnNextWave() + { + var flockConfigs = _growingPresence.Selector.GetFlocksForPresenceLevel(_growingPresence.ID, _currentLevel); + foreach (var config in flockConfigs) + { + var flock = _growingPresence.CreateAndAddFlock(config); + flock.SpawnAllMembers(); + } + _growingPresence.OnWaveSpawn(_currentLevel); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs new file mode 100644 index 000000000..bd22f1b28 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs @@ -0,0 +1,31 @@ + +namespace Perpetuum.Zones.NpcSystem.Presences +{ + public interface IPresenceConfiguration + { + int ID { get; } + + string Name { get; } + Area Area { get; } + int? SpawnId { get; } + string Note { get; } + bool Roaming { get; } + int RoamingRespawnSeconds { get; } + PresenceType PresenceType { get; } + int MaxRandomFlock { get; } + + int? RandomCenterX { get; } + int? RandomCenterY { get; } + int? RandomRadius { get; } + + int? DynamicLifeTime { get; } + bool IsRespawnAllowed { get; } + + int? InterzoneGroupId { get; } + int? GrowthSeconds { get; } + + int ZoneID { get; } + + Position RandomCenter { get; } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfigurationReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfigurationReader.cs index f6f1a481a..789160489 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfigurationReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfigurationReader.cs @@ -5,8 +5,8 @@ namespace Perpetuum.Zones.NpcSystem.Presences public interface IPresenceConfigurationReader { [CanBeNull] - PresenceConfiguration Get(int presenceID); + IPresenceConfiguration Get(int presenceID); - IEnumerable GetAll(int zoneID); + IEnumerable GetAll(int zoneID); } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs new file mode 100644 index 000000000..f158ced1e --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public interface IInterzonePresenceConfigurationReader + { + IEnumerable GetAll(); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs new file mode 100644 index 000000000..ed59c9043 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs @@ -0,0 +1,130 @@ +using Perpetuum.Log; +using Perpetuum.Timers; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public interface IInterzoneGroup + { + void Init(IZoneManager zoneManager, PresenceFactory presenceFactory); + void AddConfigs(IEnumerable presences); + void Update(TimeSpan elapsed); + int GetId(); + } + + public class InterzoneGroup : IInterzoneGroup + { + private readonly int _id; + private readonly string _name; + private readonly int _respawnTime; + private readonly double _respawnNoiseFactor; + private IntervalTimer _timer; + private IZoneManager _zoneManager; + private PresenceFactory _presenceFactory; + private readonly List _presenceConfigs = new List(); + private ImmutableList _presences = ImmutableList.Empty; + + public InterzoneGroup(int id, string name, int respawnTimeSeconds, double respawnNoise) + { + _id = id; + _name = name; + _respawnTime = respawnTimeSeconds; + _respawnNoiseFactor = respawnNoise; + ResetSpawnTimer(); + } + + public void AddConfigs(IEnumerable presences) + { + _presenceConfigs.AddMany(presences); + } + + public void Init(IZoneManager zoneManager, PresenceFactory presenceFactory) + { + _zoneManager = zoneManager; + _presenceFactory = presenceFactory; + SpawnRandom(); + } + + private void SpawnRandom() + { + ResetSpawnTimer(); + ImmutableInterlocked.Update(ref _presences, p => p.RemoveAll(q => true)); + var config = GetRandom(); + if (config == null) + { + _spawning = false; + return; + } + var zone = _zoneManager.GetZone(config.ZoneID); + var presence = _presenceFactory(zone, config); + presence.LoadFlocks(); + ImmutableInterlocked.Update(ref _presences, p => p.Add(presence)); + if (presence is INotifyPresenceExpired notifier) + notifier.PresenceExpired += OnPresenceExpired; + _spawning = false; + Logger.DebugInfo("IZ group spawned presence: " + presence.ToString()); + } + + private void OnPresenceExpired(Presence presence) + { + Logger.DebugInfo("IZ group presence expired/dead: " + presence.ToString()); + ImmutableInterlocked.Update(ref _presences, p => p.Remove(presence)); + if (presence is INotifyPresenceExpired notifier) + notifier.PresenceExpired -= OnPresenceExpired; + } + + private bool IsSpawned() + { + return !_presences.IsEmpty; + } + + private void UpdatePresence(TimeSpan elapsed) + { + foreach (var presence in _presences) + { + presence.Update(elapsed); + } + } + + private bool _spawning = false; + public void Update(TimeSpan elapsed) + { + Logger.DebugInfo(this.ToString()); + if (IsSpawned()) + { + UpdatePresence(elapsed); + return; + } + _timer.Update(elapsed); + if (_timer.Passed && !_spawning) + { + _spawning = true; + SpawnRandom(); + } + } + + private void ResetSpawnTimer() + { + var randomTime = _respawnTime * (FastRandom.NextDouble(1.0 - _respawnNoiseFactor, 1.0 + _respawnNoiseFactor)); + _timer = new IntervalTimer(TimeSpan.FromSeconds(randomTime)); + } + + private IPresenceConfiguration GetRandom() + { + return _presenceConfigs.RandomElement(); + } + + public int GetId() + { + return _id; + } + + public override string ToString() + { + return $"IZ Group: {_id} {_name} {IsSpawned()}, time to next spawn: {_timer.Interval}, elapsed:{_timer.Elapsed}, num presenses:{_presences.Count}"; + } + + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs new file mode 100644 index 000000000..76b13e6e2 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -0,0 +1,91 @@ +using System; +using System.Text; +using System.Drawing; +using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public class InterzoneRoamingPresence : InterzonePresence, IRoamingPresence + { + public StackFSM StackFSM { get; } + public Position SpawnOrigin { get; set; } + public Point CurrentRoamingPosition { get; set; } + public IRoamingPathFinder PathFinder { get; set; } + public InterzoneRoamingPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) + { + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); + + StackFSM = new StackFSM(); + StackFSM.Push(new SpawnState(this)); + } + + protected override void OnUpdate(TimeSpan time) + { + StackFSM.Update(time); + base.OnUpdate(time); + } + + protected override void OnPresenceExpired() + { + StackFSM.Clear(); + base.OnPresenceExpired(); + } + + public void OnSpawned() + { + ResetDynamicDespawnTimer(); + } + } + + public class InterzonePresence : DynamicPresence + { + public InterzonePresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) + { + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); + } + + protected override void OnFlockAdded(Flock flock) + { + flock.AllMembersDead += OnFlockAllMembersDead; + base.OnFlockAdded(flock); + } + + private void OnFlockAllMembersDead(Flock flock) + { + flock.AllMembersDead -= OnFlockAllMembersDead; + RemoveFlock(flock); + OnFlockRemoved(); + } + + protected override void OnPresenceExpired() + { + foreach (var flock in Flocks) + { + flock.AllMembersDead -= OnFlockAllMembersDead; + } + base.OnPresenceExpired(); + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(Name.ToString()); + sb.Append(Configuration.ID.ToString()); + + if (Zone != null) + sb.Append(Zone.Id.ToString()); + + return sb.ToString(); + } + + private void OnFlockRemoved() + { + if (Flocks.IsNullOrEmpty()) + OnPresenceExpired(); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs new file mode 100644 index 000000000..07dced5ff --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Perpetuum.Data; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public class InterzonePresenceConfigReader : IInterzonePresenceConfigurationReader + { + public IEnumerable GetAll() + { + var groups = Db.Query().CommandText("SELECT id, name, respawnTime, respawnNoiseFactor FROM npcinterzonegroup").Execute().Select(CreateIZGroupFromRecord).ToArray(); + + var presenceConfigs = Db.Query().CommandText("SELECT p.*, z.id as zoneID FROM npcpresence p INNER JOIN npcinterzonegroup iz ON p.izgroupid=iz.id INNER JOIN zones z ON z.spawnid=p.spawnid WHERE p.enabled=1 AND z.enabled=1") + .Execute() + .Select(CreatePresenceConfigurationFromRecord).ToArray(); + + foreach (var group in groups) + { + var configs = presenceConfigs.Where(p => group.GetId() == p.InterzoneGroupId); + group.AddConfigs(configs); + } + return groups; + } + + private static IInterzoneGroup CreateIZGroupFromRecord(IDataRecord record) + { + var id = record.GetValue("id"); + var name = record.GetValue("name"); + var respawnTimeSeconds = record.GetValue("respawnTime"); + var respawnNoise = record.GetValue("respawnNoiseFactor"); + var g = new InterzoneGroup(id, name, respawnTimeSeconds, respawnNoise); + return g; + } + + private static IPresenceConfiguration CreatePresenceConfigurationFromRecord(IDataRecord record) + { + var id = record.GetValue("id"); + var presenceType = (PresenceType)record.GetValue("presencetype"); + var topX = record.GetValue("topx"); + var topY = record.GetValue("topy"); + var bottomX = record.GetValue("bottomx"); + var bottomY = record.GetValue("bottomy"); + + var p = new PresenceConfiguration(id, presenceType) + { + Name = record.GetValue("name"), + Note = record.GetValue("note"), + SpawnId = record.GetValue("spawnid"), + Roaming = record.GetValue("roaming"), + RoamingRespawnSeconds = record.GetValue("roamingRespawnSeconds"), + MaxRandomFlock = record.GetValue("maxrandomflock") ?? 0, + RandomCenterX = record.GetValue("randomcenterx"), + RandomCenterY = record.GetValue("randomcentery"), + RandomRadius = record.GetValue("randomradius"), + DynamicLifeTime = record.GetValue("dynamiclifetime"), + IsRespawnAllowed = record.GetValue("isrespawnallowed"), + InterzoneGroupId = record.GetValue("izgroupid"), + ZoneID = record.GetValue("zoneID"), + Area = new Area(topX, topY, bottomX, bottomY) + }; + return p; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs new file mode 100644 index 000000000..cc30a6e55 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs @@ -0,0 +1,44 @@ +using Perpetuum.Threading.Process; +using System; +using System.Collections.Generic; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public class InterzonePresenceManager : Process + { + public Lazy _zoneManager; + private readonly PresenceFactory _presenceFactory; + private readonly IInterzonePresenceConfigurationReader _configurationReader; + private IEnumerable _groups = new List(); + + public InterzonePresenceManager(Lazy zoneManager, PresenceFactory presenceFactory, IInterzonePresenceConfigurationReader configurationReader) + { + _presenceFactory = presenceFactory; + _configurationReader = configurationReader; + _zoneManager = zoneManager; + } + + public override void Start() + { + _groups = _configurationReader.GetAll(); + foreach (var group in _groups) + { + group.Init(_zoneManager.Value, _presenceFactory); + } + base.Start(); + } + + public override void Update(TimeSpan time) + { + foreach (var group in _groups) + { + group.Update(time); + } + } + + public override void Stop() + { + base.Stop(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs index e99713e73..1f8356b92 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs @@ -5,6 +5,8 @@ using System.Drawing; using System.Linq; using Perpetuum.Collections; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; using Perpetuum.PathFinders; using Perpetuum.Zones.NpcSystem.Flocks; @@ -21,19 +23,45 @@ public FreeRoamingPathFinder(IZone zone) _direction = FastRandom.NextDouble(); } - public Point FindSpawnPosition(RoamingPresence presence) + private int TryGetMaxHomeRange(IRoamingPresence presence) { - var homeRange = presence.Flocks.Max(f => f.HomeRange); + try + { + return presence.Flocks.Max(f => f.HomeRange).Clamp(10, 40); + } + catch(Exception e) + { + Logger.Exception(e); + } + return 10; + } + + private double TryGetMinSlope(IRoamingPresence presence) + { + try + { + return presence.Flocks.GetMembers().Min(m => m.Slope); + } + catch (Exception e) + { + Logger.Exception(e); + } + return ZoneExtensions.MIN_SLOPE; + } + + public Point FindSpawnPosition(IRoamingPresence presence) + { + var homeRange = TryGetMaxHomeRange(presence); var rangeMax = homeRange * 2; var walkableArea = _zone.FindWalkableArea(presence.Area, rangeMax * rangeMax); return walkableArea.RandomElement(); } - public Point FindNextRoamingPosition(RoamingPresence presence) + public Point FindNextRoamingPosition(IRoamingPresence presence) { - var minSlope = presence.Flocks.GetMembers().Min(m => m.Slope); - var maxHomeRange = presence.Flocks.Max(f => f.HomeRange); + var minSlope = TryGetMinSlope(presence); + var maxHomeRange = TryGetMaxHomeRange(presence); var range = new IntRange((int) (maxHomeRange * 1.1), (int) (maxHomeRange * 1.5)); var queue = new PriorityQueue(500); @@ -50,7 +78,7 @@ public Point FindNextRoamingPosition(RoamingPresence presence) var farPosition = startNode.location.ToPosition().OffsetInDirection(_direction,FastRandom.NextDouble(range.Min,range.Max)); -// _zone.CreateAlignedDebugBeam(BeamType.red_20sec,farPosition); + _zone.CreateAlignedDebugBeam(BeamType.red_20sec,farPosition); Node current; while (queue.TryDequeue(out current)) @@ -59,7 +87,7 @@ public Point FindNextRoamingPosition(RoamingPresence presence) if (d > range.Min) { _direction = startNode.location.DirectionTo(current.location); -// _zone.CreateAlignedDebugBeam(BeamType.green_20sec,current.location.ToPosition()); + _zone.CreateAlignedDebugBeam(BeamType.green_20sec,current.location.ToPosition()); return current.location; } @@ -76,7 +104,7 @@ public Point FindNextRoamingPosition(RoamingPresence presence) if ( np.Distance(startNode.location) >= range.Max ) continue; -// _zone.CreateAlignedDebugBeam(BeamType.orange_20sec,np.ToPosition()); + _zone.CreateAlignedDebugBeam(BeamType.orange_20sec,np.ToPosition()); queue.Enqueue(new Node(np,Heuristic.Manhattan.Calculate(np.X,np.Y,(int) farPosition.X,(int) farPosition.Y))); } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/IRoamingPathFinder.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/IRoamingPathFinder.cs index 4338b54eb..2c296c466 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/IRoamingPathFinder.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/IRoamingPathFinder.cs @@ -4,7 +4,7 @@ namespace Perpetuum.Zones.NpcSystem.Presences.PathFinders { public interface IRoamingPathFinder { - Point FindSpawnPosition(RoamingPresence presence); - Point FindNextRoamingPosition(RoamingPresence presence); + Point FindSpawnPosition(IRoamingPresence presence); + Point FindNextRoamingPosition(IRoamingPresence presence); } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/NormalRoamingPathFinder.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/NormalRoamingPathFinder.cs index 348d2d439..d0495b810 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/NormalRoamingPathFinder.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/NormalRoamingPathFinder.cs @@ -17,13 +17,13 @@ public NormalRoamingPathFinder(IZone zone) _zone = zone; } - public Point FindSpawnPosition(RoamingPresence presence) + public Point FindSpawnPosition(IRoamingPresence presence) { var point = _zone.SafeSpawnPoints.GetAll().RandomElement(); return point.Location; } - public Point FindNextRoamingPosition(RoamingPresence presence) + public Point FindNextRoamingPosition(IRoamingPresence presence) { var homeRange = presence.Flocks.Max(f => f.HomeRange); var rangeMax = homeRange * 2; diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingState.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingState.cs new file mode 100644 index 000000000..9f0533962 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingState.cs @@ -0,0 +1,60 @@ +using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.Flocks; +using System; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Presences.PathFinders +{ + public class RoamingState : NullRoamingState + { + public RoamingState(IRoamingPresence presence) : base(presence) + { + } + + public override void Update(TimeSpan time) + { + if (IsRunningTask) + { + return; + } + + var members = GetAllMembers(); + + if (IsDeadAndExiting(members)) + { + return; + } + + if (IsAllNotIdle(members)) + { + return; + } + + RunTask(() => FindNextRoamingPosition(), t => { }); + } + + private bool IsAllNotIdle(Npc[] members) + { + var idleMembersCount = members.Select(m => m.AI.Current).OfType().Count(); + + return idleMembersCount < members.Length; + } + + private void FindNextRoamingPosition() + { +#if DEBUG + _presence.Log("finding new roaming position. current: " + _presence.CurrentRoamingPosition); +#endif + var nextRoamingPosition = _presence.PathFinder.FindNextRoamingPosition(_presence); +#if DEBUG + _presence.Log("next roaming position: " + nextRoamingPosition + " dist:" + _presence.CurrentRoamingPosition.Distance(nextRoamingPosition)); +#endif + _presence.CurrentRoamingPosition = nextRoamingPosition; + + foreach (var npc in _presence.Flocks.GetMembers()) + { + npc.HomePosition = _presence.CurrentRoamingPosition.ToPosition(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs new file mode 100644 index 000000000..aa58b3e9e --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -0,0 +1,206 @@ +using Perpetuum.Log; +using Perpetuum.StateMachines; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem.Flocks; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.Presences.PathFinders +{ + public abstract class CancellableState + { + private static readonly TimeSpan MAX_WAIT = TimeSpan.FromSeconds(2); + protected CancellationToken _token; + private CancellationTokenSource _source; + private Task _task; + + protected bool IsRunningTask { get; private set; } + + protected void OnEnter() + { + IsRunningTask = false; + _source = new CancellationTokenSource(); + _token = _source.Token; + } + protected void OnExit() + { + if (IsRunningTask && _task != null) + { + Logger.Warning($"Cancelling task on RoamingState"); + _source.Cancel(); + _task.Wait(MAX_WAIT); + Logger.Warning($"Cancelled!"); + } + } + protected bool IsCancelled => _token.IsCancellationRequested; + + protected void RunTask(Action action, Action continuation) + { + IsRunningTask = true; + _task = Task.Run(action, _token).ContinueWith(continuation).ContinueWith(t => IsRunningTask = false); + } + } + + public class SpawnState : CancellableState, IState + { + protected readonly IRoamingPresence _presence; + protected TimeSpan _delay = TimeSpan.Zero; + + protected bool _spawned; + protected double _repawnDelayModifier = 0.0; + + protected readonly int _playerMinDist; + + public SpawnState(IRoamingPresence presence, int playerMinDist = 200) + { + _presence = presence; + _playerMinDist = playerMinDist; + } + + protected virtual void SetSpawnDelay() + { + _delay = TimeSpan.FromSeconds(_presence.Configuration.RoamingRespawnSeconds * _repawnDelayModifier); + _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); + } + + public void Enter() + { + OnEnter(); + _spawned = false; + + _presence.SpawnOrigin = Position.Empty; + _presence.CurrentRoamingPosition = Position.Empty; + + _elapsed = TimeSpan.Zero; + + SetSpawnDelay(); + } + + public void Exit() + { + OnExit(); + } + + protected virtual void OnSpawned() + { + _presence.OnSpawned(); + _presence.StackFSM.Push(new RoamingState(_presence)); + } + + private TimeSpan _elapsed; + + private bool CheckElapsed(TimeSpan time) + { + _elapsed += time; + return _elapsed < _delay; + } + + //updated + public void Update(TimeSpan time) + { + if (IsRunningTask) + return; + + if (_spawned) + { + OnSpawned(); + return; + } + + if (CheckElapsed(time)) + return; + + RunTask(() => SpawnFlocks(), t => _spawned = true); + } + + protected virtual bool IsInRange(Position position, int range) + { + return _presence.Zone.Players.WithinRange(position, range).Any(); + } + + protected virtual bool IsValidSpawnPosition(Position position, int range) + { + return !IsInRange(position, range); + } + + protected virtual Position FindSpawnPosition() + { + return _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); + } + + private void SpawnFlocks() + { + Position spawnPosition; + int range = _playerMinDist; + bool isValidSpawnLocation; + do + { + if (IsCancelled) + { + Logger.Warning("SpawnFlocks() cancelled"); + return; + } + spawnPosition = FindSpawnPosition(); + isValidSpawnLocation = IsValidSpawnPosition(spawnPosition, range); + range--; + } while (!isValidSpawnLocation && range > 0); + + if (!isValidSpawnLocation) + { + _presence.Log("FAILED to resolve spawn position out of range of players: " + spawnPosition); + return; + } + + DoSpawning(spawnPosition); + } + + private void DoSpawning(Position spawnPosition) + { + _presence.SpawnOrigin = spawnPosition; + _presence.CurrentRoamingPosition = spawnPosition; + _presence.Log("spawn position: " + spawnPosition); + + //spawn all flocks + foreach (var flock in _presence.Flocks) + { + flock.SpawnAllMembers(); + } + } + } + + public class NullRoamingState : CancellableState, IState + { + protected readonly IRoamingPresence _presence; + + public NullRoamingState(IRoamingPresence presence) + { + _presence = presence; + } + + public virtual void Enter() { OnEnter(); } + public virtual void Exit() { OnExit(); } + + protected Npc[] GetAllMembers() + { + return _presence.Flocks.GetMembers().ToArray(); + } + + protected bool IsDeadAndExiting(Npc[] members) + { + if (members.Length <= 0) + { + _presence.StackFSM.Pop(); + return true; + } + return false; + } + + public virtual void Update(TimeSpan time) + { + var members = GetAllMembers(); + IsDeadAndExiting(members); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs index 9eac49da2..6bd1996ac 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs @@ -1,18 +1,21 @@ +using Perpetuum.Log; +using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.Flocks; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Perpetuum.Log; -using Perpetuum.Timers; -using Perpetuum.Zones.NpcSystem.Flocks; namespace Perpetuum.Zones.NpcSystem.Presences { - public class Presence : INpcGroup + public class Presence : ISmartCreatureGroup { private ImmutableHashSet _flocks = ImmutableHashSet.Empty; + private readonly IntervalTimer _updateTimer = new IntervalTimer(TimeSpan.FromSeconds(2)); - public PresenceConfiguration Configuration { get; private set; } + public int ID { get; private set; } + + public IPresenceConfiguration Configuration { get; private set; } [NotNull] public IZone Zone { get; private set; } @@ -21,34 +24,22 @@ public class Presence : INpcGroup public IEnumerable Flocks => _flocks; - public Presence(IZone zone,PresenceConfiguration configuration) - { - Zone = zone; - Configuration = configuration; - } - public FlockFactory FlockFactory { private get; set; } - protected void ClearFlocks() - { - Flock[] removedFlocks = null; + public virtual Area Area => Configuration.Area; - ImmutableInterlocked.Update(ref _flocks, f => - { - removedFlocks = f.ToArray(); - return f.Clear(); - }); + public string Name => Configuration.Name; - foreach (var flock in removedFlocks) - { - flock.RemoveAllMembersFromZone(true); - } + public IEnumerable Members + { + get { return _flocks.SelectMany(f => f.Members); } } - private void AddFlock(Flock flock) + public Presence(IZone zone, IPresenceConfiguration configuration) { - ImmutableInterlocked.Update(ref _flocks, f => f.Add(flock)); - OnFlockAdded(flock); + Zone = zone; + Configuration = configuration; + ID = Configuration.ID; } public void RemoveFlock(Flock flock) @@ -57,32 +48,14 @@ public void RemoveFlock(Flock flock) OnFlockRemoved(flock); } - protected virtual void OnFlockAdded(Flock flock) - { - Log($"Flock added. {flock.Configuration.Name}"); - flock.NpcCreated += OnFlockNpcCreated; - } - - private void OnFlockNpcCreated(Npc npc) - { - npc.SetGroup(this); - } - - private void OnFlockRemoved(Flock flock) - { - flock.NpcCreated -= OnFlockNpcCreated; - flock.RemoveAllMembersFromZone(true); - Log($"Flock removed. {flock.Configuration.Name}"); - } - - private readonly IntervalTimer _updateTimer = new IntervalTimer(TimeSpan.FromSeconds(2)); - public void Update(TimeSpan time) { _updateTimer.Update(time); - if ( !_updateTimer.Passed ) + if (!_updateTimer.Passed) + { return; + } OnUpdate(_updateTimer.Elapsed); @@ -94,58 +67,40 @@ public void Update(TimeSpan time) _updateTimer.Reset(); } - protected virtual void OnUpdate(TimeSpan time) { } - public IDictionary ToDictionary(bool withFlock = false) { var result = new Dictionary { {k.ID, Configuration.ID}, - {k.name, Configuration.name}, + {k.name, Configuration.Name}, {k.area, Area}, {k.zoneID, Zone.Id}, - {k.roaming, Configuration.roaming}, - {k.respawnSeconds, Configuration.roamingRespawnSeconds}, - {k.presenceType,(int)Configuration.presenceType}, - {k.radius, Configuration.randomRadius}, - {k.x, Configuration.randomCenterX}, - {k.y, Configuration.randomCenterY}, + {k.roaming, Configuration.Roaming}, + {k.respawnSeconds, Configuration.RoamingRespawnSeconds}, + {k.presenceType,(int)Configuration.PresenceType}, + {k.radius, Configuration.RandomRadius}, + {k.x, Configuration.RandomCenterX}, + {k.y, Configuration.RandomCenterY}, }; if (withFlock) { var counter = 0; - var dictionary = Flocks.Select(f => f.ToDictionary()).ToDictionary, string, object>(o => "f" + counter++, o => o); + var dictionary = Flocks + .Select(f => f.ToDictionary()) + .ToDictionary, string, object>(o => "f" + counter++, o => o); + result.Add(k.flock, dictionary); } return result; } - protected void CreateAndAddFlocks(IEnumerable configurations) - { - var builder = _flocks.ToBuilder(); - - foreach (var configuration in configurations) - { - var flock = CreateFlock(configuration); - builder.Add(flock); - OnFlockAdded(flock); - } - - _flocks = builder.ToImmutable(); - } - - protected Flock CreateAndAddFlock(int flockID) - { - var configuration = FlockConfigurationRepository.Get(flockID); - return CreateAndAddFlock(configuration); - } - public Flock CreateAndAddFlock(IFlockConfiguration configuration) { var flock = CreateFlock(configuration); AddFlock(flock); + return flock; } @@ -154,11 +109,10 @@ public Flock CreateFlock(IFlockConfiguration flockConfiguration) return FlockFactory(flockConfiguration, this); } - public virtual Area Area => Configuration.area; - public virtual void LoadFlocks() { var configs = FlockConfigurationRepository.GetAllByPresence(this).Where(t => t.Enabled); + CreateAndAddFlocks(configs); } @@ -167,21 +121,82 @@ public override string ToString() return $"{Name}:{Configuration.ID}:{Zone.Id}"; } - public string Name => Configuration.name; + public void AddDebugInfoToDictionary(IDictionary dictionary) + { - public IEnumerable Members + } + + public virtual void Log(string message) { - get { return _flocks.SelectMany(f => f.Members); } + Logger.Info($"[Presence] ({ToString()}) - {message}"); } - public void AddDebugInfoToDictionary(IDictionary dictionary) + protected void ClearFlocks() + { + Flock[] removedFlocks = null; + + ImmutableInterlocked.Update( + ref _flocks, + f => + { + removedFlocks = f.ToArray(); + + return f.Clear(); + }); + + foreach (var flock in removedFlocks) + { + flock.RemoveAllMembersFromZone(true); + } + } + + protected virtual void OnFlockAdded(Flock flock) { - + Log($"Flock added. {flock.Configuration.Name}"); + flock.NpcCreated += OnFlockNpcCreated; } - protected void Log(string message) + protected virtual void OnUpdate(TimeSpan time) { - Logger.Info($"[Presence] ({ToString()}) - {message}"); + } + + protected void CreateAndAddFlocks(IEnumerable configurations) + { + var builder = _flocks.ToBuilder(); + + foreach (var configuration in configurations) + { + var flock = CreateFlock(configuration); + builder.Add(flock); + OnFlockAdded(flock); + } + + _flocks = builder.ToImmutable(); + } + + protected Flock CreateAndAddFlock(int flockID) + { + var configuration = FlockConfigurationRepository.Get(flockID); + + return CreateAndAddFlock(configuration); + } + + private void AddFlock(Flock flock) + { + ImmutableInterlocked.Update(ref _flocks, f => f.Add(flock)); + OnFlockAdded(flock); + } + + private void OnFlockNpcCreated(Npc npc) + { + npc.SetGroup(this); + } + + private void OnFlockRemoved(Flock flock) + { + flock.NpcCreated -= OnFlockNpcCreated; + flock.RemoveAllMembersFromZone(true); + Log($"Flock removed. {flock.Configuration.Name}"); } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs index 6b12ea28e..b6810405b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs @@ -2,37 +2,43 @@ namespace Perpetuum.Zones.NpcSystem.Presences { - public class PresenceConfiguration + public class PresenceConfiguration : IPresenceConfiguration { public int ID { get; private set; } - public string name; - public Area area; - public int? spawnId; - public string note; - public bool roaming; // kell-e meg? - public int roamingRespawnSeconds; - public readonly PresenceType presenceType; - public int maxRandomFlock; + public string Name { get; set; } + public Area Area { get; set; } + public int? SpawnId { get; set; } + public string Note { get; set; } + public bool Roaming { get; set; } + public int RoamingRespawnSeconds { get; set; } + public PresenceType PresenceType { get; set; } + public int MaxRandomFlock { get; set; } - public int? randomCenterX; - public int? randomCenterY; - public int? randomRadius; + public int? RandomCenterX { get; set; } + public int? RandomCenterY { get; set; } + public int? RandomRadius { get; set; } - public int? dynamicLifeTime; - public bool isRespawnAllowed; + public int? DynamicLifeTime { get; set; } + public bool IsRespawnAllowed { get; set; } - public Position RandomCenter => new Position((double) randomCenterX,(double) randomCenterY); + public int? InterzoneGroupId { get; set; } - public PresenceConfiguration(int id,PresenceType presenceType) + public int? GrowthSeconds { get; set; } + + public int ZoneID { get; set; } + + public Position RandomCenter => new Position((double)RandomCenterX, (double)RandomCenterY); + + public PresenceConfiguration(int id, PresenceType presenceType) { ID = id; - this.presenceType = presenceType; + this.PresenceType = presenceType; } public override string ToString() { - return $" ID:{ID} name:{name} area:{area} spawnID:{spawnId} note:{note}"; + return $" ID:{ID} name:{Name} area:{Area} spawnID:{SpawnId} note:{Note}"; } } @@ -40,12 +46,12 @@ public class DirectPresenceConfiguration : PresenceConfiguration { private static readonly IIDGenerator _idGenerator = IDGenerator.CreateIntIDGenerator(25000); - public DirectPresenceConfiguration(IZone zone) : base(_idGenerator.GetNextID(),PresenceType.Direct) + public DirectPresenceConfiguration(IZone zone) : base(_idGenerator.GetNextID(), PresenceType.Direct) { - area = zone.Configuration.Size.ToArea(); - name = "direct presence " + ID; - spawnId = 10; //dynamic kamubol, szerintem kicsit sem kell - note = "abs! rulez"; + Area = zone.Configuration.Size.ToArea(); + Name = "direct presence " + ID; + SpawnId = 10; //dynamic kamubol, szerintem kicsit sem kell + Note = "abs! rulez"; } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs index 915e598b3..95f21e312 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs @@ -7,27 +7,27 @@ namespace Perpetuum.Zones.NpcSystem.Presences { public class PresenceConfigurationReader : IPresenceConfigurationReader { - public PresenceConfiguration Get(int presenceID) + public IPresenceConfiguration Get(int presenceID) { - var record = Db.Query().CommandText("select * from npcpresence where id = @presenceID and enabled = 1").SetParameter("@presenceID", presenceID).ExecuteSingleRow(); + var record = Db.Query().CommandText("select * from npcpresence where id = @presenceID and enabled = 1 and izgroupid IS NULL").SetParameter("@presenceID", presenceID).ExecuteSingleRow(); if (record == null) return null; return CreatePresenceConfigurationFromRecord(record); } - public IEnumerable GetAll(int zoneID) + public IEnumerable GetAll(int zoneID) { - return Db.Query().CommandText("select p.* from npcpresence p inner join zones z on p.spawnid = z.spawnid where z.id = @zoneID and p.enabled = 1") + return Db.Query().CommandText("select p.* from npcpresence p inner join zones z on p.spawnid = z.spawnid where z.id = @zoneID and p.enabled = 1 and p.izgroupid IS NULL") .SetParameter("@zoneID", zoneID) .Execute() .Select(CreatePresenceConfigurationFromRecord).ToArray(); } - private static PresenceConfiguration CreatePresenceConfigurationFromRecord(IDataRecord record) + private static IPresenceConfiguration CreatePresenceConfigurationFromRecord(IDataRecord record) { var id = record.GetValue("id"); - var presenceType = (PresenceType) record.GetValue("presencetype"); + var presenceType = (PresenceType)record.GetValue("presencetype"); var topX = record.GetValue("topx"); var topY = record.GetValue("topy"); var bottomX = record.GetValue("bottomx"); @@ -35,18 +35,19 @@ private static PresenceConfiguration CreatePresenceConfigurationFromRecord(IData var p = new PresenceConfiguration(id, presenceType) { - name = record.GetValue("name"), - note = record.GetValue("note"), - spawnId = record.GetValue("spawnid"), - roaming = record.GetValue("roaming"), - roamingRespawnSeconds = record.GetValue("roamingRespawnSeconds"), - maxRandomFlock = record.GetValue("maxrandomflock") ?? 0, - randomCenterX = record.GetValue("randomcenterx"), - randomCenterY = record.GetValue("randomcentery"), - randomRadius = record.GetValue("randomradius"), - dynamicLifeTime = record.GetValue("dynamiclifetime"), - isRespawnAllowed = record.GetValue("isrespawnallowed"), - area = new Area(topX, topY, bottomX, bottomY) + Name = record.GetValue("name"), + Note = record.GetValue("note"), + SpawnId = record.GetValue("spawnid"), + Roaming = record.GetValue("roaming"), + RoamingRespawnSeconds = record.GetValue("roamingRespawnSeconds"), + MaxRandomFlock = record.GetValue("maxrandomflock") ?? 0, + RandomCenterX = record.GetValue("randomcenterx"), + RandomCenterY = record.GetValue("randomcentery"), + RandomRadius = record.GetValue("randomradius"), + DynamicLifeTime = record.GetValue("dynamiclifetime"), + IsRespawnAllowed = record.GetValue("isrespawnallowed"), + GrowthSeconds = record.GetValue("growthseconds"), + Area = new Area(topX, topY, bottomX, bottomY) }; return p; } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceFactory.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceFactory.cs index 502d02927..ecdcdb404 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceFactory.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceFactory.cs @@ -1,5 +1,5 @@ namespace Perpetuum.Zones.NpcSystem.Presences { - public delegate Presence PresenceFactory(IZone zone,PresenceConfiguration configuration); + public delegate Presence PresenceFactory(IZone zone, IPresenceConfiguration configuration); } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs index e4169f117..1d3477dec 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs @@ -27,6 +27,11 @@ public void LoadAll() foreach (var configuration in _configurationReader.GetAll(_zone.Id)) { var presence = CreatePresence(configuration); + if (presence == null) + { + Logger.Warning($"Failed to load presence: {configuration.ID}"); + continue; + } presence.LoadFlocks(); AddPresence(presence); } @@ -46,7 +51,7 @@ public Presence CreatePresence(int presenceID) return CreatePresence(configuration); } - private Presence CreatePresence(PresenceConfiguration configuration) + private Presence CreatePresence(IPresenceConfiguration configuration) { return _presenceFactory(_zone,configuration); } @@ -62,7 +67,7 @@ public void AddPresence(Presence presence) private void OnPresenceExpired(Presence presence) { ImmutableInterlocked.Update(ref _presences, p => p.Remove(presence)); - Logger.Info("Presence expired. " + presence.Configuration.name); + Logger.Info("Presence expired. " + presence.Configuration.Name); } public override void Update(TimeSpan time) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs index 2920b46a7..d872d3802 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs @@ -8,6 +8,12 @@ public enum PresenceType Random = 3, DynamicPool = 4, FreeRoaming = 5, - Direct = 6 + Direct = 6, + Interzone = 7, + InterzoneRoaming = 8, + DynamicExtended = 9, + ExpiringRandom = 10, + EscalatingRandomPresence = 11, + GrowingNPCBasePresence = 12 } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/RandomPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/RandomPresence.cs index 4102d3b0d..64ee6d792 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/RandomPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/RandomPresence.cs @@ -10,14 +10,14 @@ public class RandomPresence : Presence private readonly IRandomFlockSelector _randomFlockSelector; private ImmutableHashSet _nextRandomFlocks = ImmutableHashSet.Empty; - public RandomPresence(IZone zone,PresenceConfiguration configuration,IRandomFlockSelector randomFlockSelector) : base(zone,configuration) + public RandomPresence(IZone zone, IPresenceConfiguration configuration,IRandomFlockSelector randomFlockSelector) : base(zone,configuration) { _randomFlockSelector = randomFlockSelector; } public override void LoadFlocks() { - for (var i = 0; i < Configuration.maxRandomFlock; i++) + for (var i = 0; i < Configuration.MaxRandomFlock; i++) { var randomFlock = _randomFlockSelector.SelectRandomFlockByPresence(this); SpawnRandomFlock(randomFlock); @@ -42,7 +42,7 @@ private void OnFlockAllMembersDead(Flock flock) ImmutableInterlocked.Update(ref _nextRandomFlocks, f => { - if (f.Count >= Configuration.maxRandomFlock) + if (f.Count >= Configuration.MaxRandomFlock) return f; var randomFlockSpawner = new RandomFlockSpawner(this, _randomFlockSelector); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs index 16c37c06d..e3a8a580e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs @@ -1,169 +1,45 @@ using System; +using System.Collections.Generic; using System.Drawing; -using System.Linq; -using System.Threading.Tasks; using Perpetuum.StateMachines; -using Perpetuum.Units; using Perpetuum.Zones.NpcSystem.Flocks; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; namespace Perpetuum.Zones.NpcSystem.Presences { - public class RoamingPresence : Presence + public interface IRoamingPresence { - private readonly StackFSM _fsm; - public Position SpawnOrigin { get; private set; } - public Point CurrentRoamingPosition { get; private set; } - - public RoamingPresence(IZone zone,PresenceConfiguration configuration) : base(zone,configuration) - { - _fsm = new StackFSM(); - _fsm.Push(new SpawnState(this)); - } + StackFSM StackFSM { get; } + Position SpawnOrigin { get; set; } + Point CurrentRoamingPosition { get; set; } + IRoamingPathFinder PathFinder { get; set; } + IPresenceConfiguration Configuration { get; } + IZone Zone { get; } + IEnumerable Flocks { get; } + Area Area { get; } + void Log(string message); + void OnSpawned(); + } + public class RoamingPresence : Presence, IRoamingPresence + { + public StackFSM StackFSM { get; } + public Position SpawnOrigin { get; set; } + public Point CurrentRoamingPosition { get; set; } public IRoamingPathFinder PathFinder { get; set; } + public override Area Area => Configuration.Area; - protected override void OnUpdate(TimeSpan time) + public RoamingPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { - _fsm.Update(time); + StackFSM = new StackFSM(); + StackFSM.Push(new SpawnState(this)); } - #region Roaming States - - private class SpawnState : IState - { - private readonly RoamingPresence _presence; - private TimeSpan _delay = TimeSpan.Zero; - - private bool _spawning; - private bool _spawned; - private double _repawnDelayModifier = 0.0; - - public SpawnState(RoamingPresence presence) - { - _presence = presence; - } - - public void Enter() - { - _spawning = false; - _spawned = false; - - _elapsed = TimeSpan.Zero; - - _delay = TimeSpan.FromSeconds(_presence.Configuration.roamingRespawnSeconds * _repawnDelayModifier); - _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); - } - - public void Exit() - { - - } - - private TimeSpan _elapsed; - - //updated - public void Update(TimeSpan time) - { - if (_spawning) - return; - - if (_spawned) - { - _presence._fsm.Push(new RoamingState(_presence)); - return; - } - - _elapsed += time; - - if (_elapsed < _delay) - return; - - _spawning = true; - Task.Run(() => SpawnFlocks()).ContinueWith(t => - { - _spawned = true; - _spawning = false; - }); - } - - private void SpawnFlocks() - { - Position spawnPosition; - bool anyPlayersAround; - - do - { - spawnPosition = _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); - anyPlayersAround = _presence.Zone.Players.WithinRange(spawnPosition, 200).Any(); - - } while (anyPlayersAround); - - _presence.SpawnOrigin = spawnPosition; - _presence.CurrentRoamingPosition = spawnPosition; - _presence.Log("spawn position: " + spawnPosition); - - //spawn all flocks - foreach (var flock in _presence.Flocks) - { - flock.SpawnAllMembers(); - } - } - } - - private class RoamingState : IState + protected override void OnUpdate(TimeSpan time) { - private readonly RoamingPresence _presence; - - public RoamingState(RoamingPresence presence) - { - _presence = presence; - } - - public void Enter() - { - } - - public void Exit() - { - } - - private bool _finding; - - public void Update(TimeSpan time) - { - if (_finding) - return; - - var members = _presence.Flocks.GetMembers().ToArray(); - if (members.Length <= 0) - { - _presence._fsm.Pop(); - return; - } - - var idleMembersCount = members.Select(m => m.AI.Current).OfType().Count(); - if (idleMembersCount < members.Length) - return; - - _finding = true; - Task.Run(() => FindNextRoamingPosition()).ContinueWith(t => _finding = false); - } - - private void FindNextRoamingPosition() - { - _presence.Log("finding new roaming position. current: " + _presence.CurrentRoamingPosition); - var nextRoamingPosition = _presence.PathFinder.FindNextRoamingPosition(_presence); - _presence.Log("next roaming position: " + nextRoamingPosition + " dist:" + _presence.CurrentRoamingPosition.Distance(nextRoamingPosition)); - _presence.CurrentRoamingPosition = nextRoamingPosition; - - foreach (var npc in _presence.Flocks.GetMembers()) - { - npc.HomePosition = _presence.CurrentRoamingPosition.ToPosition(); - } - } + StackFSM.Update(time); } - #endregion + public void OnSpawned() { } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs new file mode 100644 index 000000000..1240a87eb --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs @@ -0,0 +1,33 @@ +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + /// + /// Represents a single npc presence and its spawned state + /// + public interface INpcReinforcementWave + { + int PresenceId { get; } + DynamicPresence ActivePresence { get; } + double Threshold { get; } + bool Spawned { get; } + + /// + /// Set the ActivePresence and mark this wave as "Spawned" + /// + /// presence that is spawned to the zone + void SetActivePresence(DynamicPresence presence); + + /// + /// Test if the provided presence is the ActivePresence of this wave + /// + /// Presence to compare + /// true if reference is equal + bool IsActivePresence(Presence presence); + + /// + /// Deactivate ActivePresence + /// + void DeactivatePresence(); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs new file mode 100644 index 000000000..2985a94db --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs @@ -0,0 +1,37 @@ +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + /// + /// Ore NPC Spawn - a list of presences that spawn at particular percentages of some activity completion + /// + public interface INpcReinforcements + { + /// + /// Gets the next IOreNpcData for the provided threshold percent mined + /// + /// double of range [0.-1.] representing a percentage + /// IOreNpcData + INpcReinforcementWave GetNextPresence(double threshold); + + /// + /// Does this INpcReinforcements have the provided Presence active? + /// + /// Presence to compare + /// true if provided presence is spawned in this INpcReinforcements + bool HasActivePresence(Presence presence); + + /// + /// Get the INpcReinforcementWave of some active Presence + /// + /// Presence to compare + /// INpcReinforcementWave of spawned presence + INpcReinforcementWave GetActiveWaveOfPresence(Presence presence); + + /// + /// Get all active waves (waves with active presence) + /// + /// INpcReinforcementWaves with active presences + INpcReinforcementWave[] GetAllActiveWaves(); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs new file mode 100644 index 000000000..91c3b64dd --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs @@ -0,0 +1,13 @@ +using Perpetuum.Zones.Terrains.Materials; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + /// + /// DB lookup interface and factory for INpcReinforcements + /// + public interface INpcReinforcementsRepository + { + INpcReinforcements CreateOreNPCSpawn(MaterialType materialType, int zoneId); + INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo, int zoneId); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs new file mode 100644 index 000000000..52d53557b --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs @@ -0,0 +1,39 @@ +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + public class NpcReinforcementWave : INpcReinforcementWave + { + public int PresenceId { get; } + public double Threshold { get; } + public bool Spawned { get; private set; } + public DynamicPresence ActivePresence { get; private set; } + + public NpcReinforcementWave(int presenceID, double threshold) + { + PresenceId = presenceID; + Threshold = threshold; + } + + public override string ToString() + { + return $"{Threshold}:{PresenceId} Spawned? {Spawned}"; + } + + public void SetActivePresence(DynamicPresence presence) + { + ActivePresence = presence; + Spawned = true; + } + + public bool IsActivePresence(Presence presence) + { + return ReferenceEquals(ActivePresence, presence); + } + + public void DeactivatePresence() + { + ActivePresence = null; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs new file mode 100644 index 000000000..b9a58722a --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs @@ -0,0 +1,62 @@ +using Perpetuum.Zones.NpcSystem.Presences; +using System; +using System.Linq; +using System.Text; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + public class NpcReinforcements : INpcReinforcements + { + // Sorted array of OreNpcData by threshold + private readonly INpcReinforcementWave[] _presences; + + public NpcReinforcements(INpcReinforcementWave[] presences) + { + _presences = presences.OrderBy(s => Array.IndexOf(presences, s.Threshold)).ToArray(); + } + + /// + /// Get the next unspawned presence in the list with the next greatest threshold + /// + /// percentage expressed as [0.0-1.0] + /// INpcReinforcementWave or null + public INpcReinforcementWave GetNextPresence(double threshold) + { + for (var i = _presences.Length - 1; i >= 0; i--) + { + if (_presences[i].Threshold < threshold) + { + return _presences[i].Spawned ? null : _presences[i]; + } + } + return null; + } + + public bool HasActivePresence(Presence presence) + { + return _presences.Any(w => w.IsActivePresence(presence)); + } + + public INpcReinforcementWave GetActiveWaveOfPresence(Presence presence) + { + return _presences.Single(w => w.IsActivePresence(presence)); + } + + public INpcReinforcementWave[] GetAllActiveWaves() + { + return _presences.Where(w => !w.IsActivePresence(null)).ToArray(); + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine("ReinforceSpawn {"); + for (var i = 0; i < _presences.Length; i++) + { + sb.AppendLine(_presences[i].ToString()); + } + sb.AppendLine("}"); + return sb.ToString(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs new file mode 100644 index 000000000..559532600 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs @@ -0,0 +1,42 @@ +using Perpetuum.Data; +using Perpetuum.Zones.Terrains.Materials; +using System.Data; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + public class NpcReinforcementsRepository : INpcReinforcementsRepository + { + private const string queryStr = "SELECT threshold, presenceId from npcreinforcements WHERE targetId=@target AND reinforcementType=@type AND (zoneId IS NULL OR zoneId=@zone);"; + + public INpcReinforcements CreateOreNPCSpawn(MaterialType materialType, int zoneId) + { + var records = Db.Query().CommandText(queryStr) + .SetParameter("@target", materialType) + .SetParameter("@type", ReinforcementType.Minerals) + .SetParameter("@zone", zoneId) + .Execute() + .Select(CreateFromRecord).ToArray(); + return new NpcReinforcements(records); + } + + private static INpcReinforcementWave CreateFromRecord(IDataRecord record) + { + var presence = record.GetValue("presenceId"); + var threshold = record.GetValue("threshold"); + var pair = new NpcReinforcementWave(presence, threshold); + return pair; + } + + public INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo, int zoneId) + { + var records = Db.Query().CommandText(queryStr) + .SetParameter("@target", npcBossInfo.FlockId) + .SetParameter("@type", ReinforcementType.Boss) + .SetParameter("@zone", zoneId) + .Execute() + .Select(CreateFromRecord).ToArray(); + return new NpcReinforcements(records); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs new file mode 100644 index 000000000..e813739de --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs @@ -0,0 +1,8 @@ +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + public enum ReinforcementType + { + Minerals = 1, + Boss = 2 + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs new file mode 100644 index 000000000..37026158d --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -0,0 +1,497 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Players; +using Perpetuum.StateMachines; +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Zones.DamageProcessors; +using Perpetuum.Zones.Locking; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.NpcSystem.AI.CombatDrones; +using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement; +using Perpetuum.Zones.NpcSystem.ThreatManaging; +using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Scanning.Results; +using Perpetuum.Zones.Terrains; +using Perpetuum.Zones.Terrains.Materials; +using Perpetuum.Zones.Terrains.Materials.Minerals; +using Perpetuum.Zones.Terrains.Materials.Plants; +using System; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem +{ + public class SmartCreature : Creature + { + private const double AggroRange = 30; + private const double BestComnatRangeModifier = 0.9; + private const double BaseCallForHelpArmorThreshold = 0.2; + private readonly TimeKeeper debounceBodyPull = new TimeKeeper(TimeSpan.FromSeconds(2.5)); + private readonly TimeKeeper debounceLockChange = new TimeKeeper(TimeSpan.FromSeconds(2.5)); + private readonly IntervalTimer pseudoUpdateFreq = new IntervalTimer(TimeSpan.FromMilliseconds(650)); + private Lazy maxActionRange; + private Lazy optimalActionRange; + private TimeSpan lastHelpCalled; + + [CanBeNull] + private ISmartCreatureGroup group; + + public StackFSM AI { get; private set; } + + public Behavior Behavior { get; set; } + + public double HomeRange { get; set; } + + public Position HomePosition { get; set; } + + public ThreatManager ThreatManager { get; } + + public PseudoThreatManager PseudoThreatManager { get; } + + public IndustrialValueManager IndustrialValueManager { get; } + + public int BestActionRange => optimalActionRange.Value; + + public int MaxActionRange => maxActionRange.Value; + + public bool IsInHomeRange => CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); + + public ISmartCreatureGroup Group => group; + + public virtual bool IsStationary => MaxSpeed.IsZero(); + + public virtual double CallForHelpArmorThreshold => BaseCallForHelpArmorThreshold; + + public bool CallForHelp { private get; set; } + + public NpcBossInfo BossInfo { get; set; } + + public SmartCreature() + { + RecalculateMaxCombatRange(); + RecalculateOptimalCombatRange(); + ThreatManager = new ThreatManager(); + AI = new StackFSM(); + PseudoThreatManager = new PseudoThreatManager(); + IndustrialValueManager = new IndustrialValueManager(); + } + + public virtual void LookingForHostiles() + { + foreach (IUnitVisibility visibility in GetVisibleUnits()) + { + AddBodyPullThreat(visibility.Target); + } + } + + public void LookingForMiningTargets() + { + IndustrialValueManager.Clear(); + + Area area = Zone.CreateArea(CurrentPosition, BestActionRange); + + MaterialType[] availableMaterialTypes = Zone.Terrain.GetAvailableMineralTypes(); + + foreach (MaterialType materialType in availableMaterialTypes) + { + MineralScanResultBuilder builder = MineralScanResultBuilder.Create(Zone, materialType); + + builder.ScanArea = area; + builder.ScanAccuracy = 100.0; + + MineralScanResult result = builder.Build(); + + + if (Zone.Terrain.GetMaterialLayer(materialType) is MineralLayer mineralLayer) + { + if (result.FoundAny) + { + System.Collections.Generic.IEnumerable valuablePositions = result.Area + .GetPositions() + .Where(x => mineralLayer.HasMineral(x)) + .Select(x => Zone.FixZ(x)) + .Where(x => x.IsInRangeOf3D(PositionWithHeight, BestActionRange)); + + foreach (Position valuablePosition in valuablePositions) + { + MineralNode mineralNode = mineralLayer.GetNode(valuablePosition); + + _ = IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Mineral, mineralNode.GetValue(valuablePosition)); + } + } + } + } + } + + public void LookingForHarvestingTargets() + { + IndustrialValueManager.Clear(); + + Area area = Zone.CreateArea(CurrentPosition, BestActionRange); + + PlantType[] availablePlantTypes = Zone.Terrain.GetAvailablePlantTypes(); + + foreach (PlantType plantType in availablePlantTypes) + { + System.Collections.Generic.List plants = Zone.GetPlantPositionsInArea(plantType, area); + + if (plants.Any()) + { + System.Collections.Generic.IEnumerable valuablePositions = plants + .Select(x => Zone.FixZ(x)) + .Where(x => x.IsInRangeOf2D(CurrentPosition, BestActionRange)); + + foreach (Position valuablePosition in valuablePositions) + { + PlantInfo plant = Zone.Terrain.Plants.GetValue(valuablePosition.intX, valuablePosition.intY); + + if (plant.material > 0) + { + _ = IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Plant, plant.material); + } + } + } + } + } + + public void AddPseudoThreat(Unit hostile) + { + PseudoThreatManager.AddOrRefreshExisting(hostile); + } + + public void SetGroup(ISmartCreatureGroup group) + { + this.group = group; + } + + public void RecalculateOptimalCombatRange() + { + optimalActionRange = new Lazy(CalculateCombatRange); + } + + public void RecalculateMaxCombatRange() + { + maxActionRange = new Lazy(CalculateMaxCombatRange); + } + + protected override void OnPropertyChanged(ItemProperty property) + { + base.OnPropertyChanged(property); + + switch (property.Field) + { + case AggregateField.locking_range: + { + RecalculateOptimalCombatRange(); + RecalculateMaxCombatRange(); + + break; + } + case AggregateField.armor_current: + { + double percentage = Armor.Ratio(ArmorMax); + + if (percentage <= CallForHelpArmorThreshold) + { + CallingForHelp(); + } + + break; + } + } + } + + protected override void OnUnitLockStateChanged(Lock @lock) + { + if (!debounceLockChange.Expired) + { + return; + } + + + if (!(@lock is UnitLock unitLock)) + { + return; + } + + if (unitLock.Target != this) + { + return; + } + + if (unitLock.State != LockState.Locked) + { + return; + } + + double threatValue = unitLock.Primary + ? Threat.LOCK_PRIMARY + : Threat.LOCK_SECONDARY; + + AddThreat(unitLock.Owner, new Threat(ThreatType.Lock, threatValue), true); + debounceLockChange.Reset(); + } + + protected override void OnUnitTileChanged(Unit target) + { + if (!debounceBodyPull.Expired) + { + return; + } + + AddBodyPullThreat(target); + debounceBodyPull.Reset(); + } + + protected void AddBodyPullThreat(Unit enemy) + { + if (!IsHostile(enemy)) + { + return; + } + + BodyPullThreatHelper helper = new BodyPullThreatHelper(this); + + enemy.AcceptVisitor(helper); + } + + private int CalculateCombatRange() + { + double range = (int)ActiveModules + .Where(m => m.IsRanged) + .Select(module => module.OptimalRange) + .Concat(new[] { MaxTargetingRange }) + .Min(); + range *= BestComnatRangeModifier; + range = Math.Max(3, range); + + return (int)range; + } + + private int CalculateMaxCombatRange() + { + double range = ActiveModules + .Where(m => m.IsRanged) + .Select(module => (int)(module.OptimalRange + module.Falloff)) + .Max(); + range = Math.Max(3, range); + + return (int)range; + } + + public bool IsInAggroRange(Unit target) + { + return this is CombatDrone || IsStationary || IsInRangeOf3D(target, AggroRange); + } + + public virtual void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) + { + if (hostile is RemoteControlledCreature) + { + threat = Threat.Multiply(threat, 100); + } + + if (hostile.IsPlayer()) + { + BossInfo?.OnAggro(hostile as Player); + } + + ThreatManager.GetOrAddHostile(hostile).AddThreat(threat); + + RemovePseudoThreat(hostile); + + if (!spreadToGroup) + { + return; + } + + ISmartCreatureGroup group = Group; + if (@group == null) + { + return; + } + + Threat multipliedThreat = Threat.Multiply(threat, 0.5); + + foreach (SmartCreature member in @group.Members) + { + if (member == this) + { + continue; + } + + member.AddThreat(hostile, multipliedThreat, false); + } + } + + /// + /// This determines if threat can be added to a target based on the following: + /// - Is the target already on the threat manager + /// - Or is the npc non-passive and the Threat is of some defined type + /// + /// Unit target + /// Threat threat + /// If the target can be a threat + public bool CanAddThreatTo(Unit target, Threat threat) + { + return ThreatManager.Contains(target) || (Behavior.Type != BehaviorType.Passive && threat.type != ThreatType.Undefined); + } + + public void AddAssistThreat(Unit assistant, Unit target, Threat threat) + { + if (!ThreatManager.Contains(target)) + { + return; + } + + if (!CanAddThreatTo(assistant, threat)) + { + return; + } + + AddThreat(assistant, threat, true); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + public virtual bool IsFriendly(Unit source) + { + return false; + } + + protected override void OnTileChanged() + { + base.OnTileChanged(); + LookingForHostiles(); + } + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + States.Aggressive = Behavior.Type == BehaviorType.Aggressive; + + base.OnEnterZone(zone, enterType); + + if (this is SentryTurret) + { + AI.Push(new SentryTurretCombatAI(this)); + } + else if (this is IndustrialTurret) + { + if ((this as IndustrialTurret).TurretType == TurretType.Mining) + { + AI.Push(new MiningIndustrialTurretAI(this)); + } + else + { + AI.Push(new HarvestingIndustrialTurretAI(this)); + } + } + else if (this is CombatDrone) + { + AI.Push(new GuardCombatDroneAI(this)); + } + else if (IsStationary) + { + AI.Push(new StationaryIdleAI(this)); + } + else + { + AI.Push(new IdleAI(this)); + } + } + + protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) + { + base.OnDamageTaken(source, e); + + Player player = Zone.ToPlayerOrGetOwnerPlayer(source); + + if (player == null) + { + return; + } + + BossInfo?.OnDamageTaken(this, player); + + if (!IsFriendly(source)) + { + AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); + } + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + + AI.Update(time); + + UpdatePseudoThreats(time); + } + + private void RemovePseudoThreat(Unit hostile) + { + PseudoThreatManager.Remove(hostile); + } + + private void UpdatePseudoThreats(TimeSpan time) + { + _ = pseudoUpdateFreq.Update(time); + + if (pseudoUpdateFreq.Passed) + { + PseudoThreatManager.Update(pseudoUpdateFreq.Elapsed); + pseudoUpdateFreq.Reset(); + } + } + + private void CallingForHelp() + { + if (!CallForHelp) + { + return; + } + + if (!GlobalTimer.IsPassed(ref lastHelpCalled, TimeSpan.FromSeconds(5))) + { + return; + } + + ISmartCreatureGroup group = Group; + + if (group == null) + { + return; + } + + foreach (SmartCreature member in group.Members.Where(flockMember => flockMember != this)) + { + member.HelpingFor(this); + } + } + + private void HelpingFor(SmartCreature caller) + { + if (Armor.Ratio(ArmorMax) < CallForHelpArmorThreshold) + { + return; + } + + ThreatManager.Clear(); + + foreach (Hostile hostile in caller.ThreatManager.Hostiles) + { + AddThreat(hostile.Unit, new Threat(ThreatType.Undefined, hostile.Threat), true); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs new file mode 100644 index 000000000..d6b6baf9b --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs @@ -0,0 +1,12 @@ +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public enum CombatPrimaryLockSelectionStrategy + { + Random, + Hostile, + Closest, + OptimalRange, + HostileOrClosest, + PropagatedPrimary, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategyBuilder.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategyBuilder.cs new file mode 100644 index 000000000..5aaf6e13e --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategyBuilder.cs @@ -0,0 +1,26 @@ +using Perpetuum.Collections; + +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public class CombatPrimaryLockSelectionStrategyBuilder + { + private readonly WeightedCollection selection; + + public CombatPrimaryLockSelectionStrategyBuilder() + { + selection = new WeightedCollection(); + } + + public CombatPrimaryLockSelectionStrategyBuilder WithStrategy(CombatPrimaryLockSelectionStrategy strategy, int weight = 1) + { + selection.Add(strategy, weight); + + return this; + } + + public CombatPrimaryLockSelectionStrategySelector Build() + { + return new CombatPrimaryLockSelectionStrategySelector(selection); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategySelector.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategySelector.cs new file mode 100644 index 000000000..852b7abc4 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategySelector.cs @@ -0,0 +1,27 @@ +using Perpetuum.Collections; +using Perpetuum.Zones.Locking.Locks; + +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public class CombatPrimaryLockSelectionStrategySelector + { + private readonly WeightedCollection primaryLockSelectionStrategies; + + public CombatPrimaryLockSelectionStrategySelector(WeightedCollection primaryLockSelectionStrategies) + { + this.primaryLockSelectionStrategies = primaryLockSelectionStrategies; + } + + public bool TryUseStrategy(SmartCreature smartCreature, UnitLock[] locks) + { + var primaryLockSelectionStrategy = primaryLockSelectionStrategies.GetRandom(); + + return CombatStrategies.TryInvokeStrategy(primaryLockSelectionStrategy, smartCreature, locks); + } + + public static CombatPrimaryLockSelectionStrategyBuilder Create() + { + return new CombatPrimaryLockSelectionStrategyBuilder(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs new file mode 100644 index 000000000..eca6ccc23 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs @@ -0,0 +1,100 @@ +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public delegate bool CombatTargetSelectionStrategy(SmartCreature smartCreature, UnitLock[] locks); + + public static class CombatStrategies + { + public static Dictionary All = + new Dictionary() + { + { CombatPrimaryLockSelectionStrategy.Random, TargetRandom }, + { CombatPrimaryLockSelectionStrategy.Hostile, TargetMostHated }, + { CombatPrimaryLockSelectionStrategy.Closest, TargetClosest }, + { CombatPrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal }, + { CombatPrimaryLockSelectionStrategy.HostileOrClosest, TargetMostHatedOrClosest }, + { CombatPrimaryLockSelectionStrategy.PropagatedPrimary, TargetPropagatedPrimary }, + }; + + public static CombatTargetSelectionStrategy GetStrategy(CombatPrimaryLockSelectionStrategy strategyType) + { + return All.GetOrDefault(strategyType); + } + + public static bool TryInvokeStrategy(CombatPrimaryLockSelectionStrategy strategyType, SmartCreature smartCreature, UnitLock[] locks) + { + var primaryLockSelectionStrategy = GetStrategy(strategyType); + + if (primaryLockSelectionStrategy == null) + { + return false; + } + + return primaryLockSelectionStrategy(smartCreature, locks); + } + + private static bool TargetPropagatedPrimary(SmartCreature smartCreature, UnitLock[] locks) + { + var propagatedPrimary = locks.FirstOrDefault(x => x.Target == ((smartCreature as RemoteControlledCreature).CommandRobot.GetPrimaryLock() as UnitLock)?.Target); + + if (propagatedPrimary == null) + { + smartCreature.ResetLocks(); + } + + return TrySetPrimaryLock(smartCreature, propagatedPrimary); + } + + private static bool TargetMostHated(SmartCreature smartCreature, UnitLock[] locks) + { + var hostiles = smartCreature.ThreatManager.Hostiles; + var hostileLocks = locks.Where(u => hostiles.Any(h => h.Unit.Eid == u.Target.Eid)); + var mostHostileLock = hostileLocks + .OrderByDescending(u => hostiles.Where(h => h.Unit.Eid == u.Target.Eid).FirstOrDefault()?.Threat ?? 0) + .FirstOrDefault(); + + return TrySetPrimaryLock(smartCreature, mostHostileLock); + } + + private static bool TargetWithinOptimal(SmartCreature smartCreature, UnitLock[] locks) + { + return TrySetPrimaryLock( + smartCreature, + locks + .Where(k => k.Target.GetDistance(smartCreature) < smartCreature.BestActionRange) + .RandomElement()); + } + + private static bool TargetClosest(SmartCreature smartCreature, UnitLock[] locks) + { + return TrySetPrimaryLock(smartCreature, locks.OrderBy(u => u.Target.GetDistance(smartCreature)).First()); + } + + private static bool TargetRandom(SmartCreature smartCreature, UnitLock[] locks) + { + return TrySetPrimaryLock(smartCreature, locks.RandomElement()); + } + + private static bool TargetMostHatedOrClosest(SmartCreature smartCreature, UnitLock[] locks) + { + return TargetMostHated(smartCreature, locks) || TargetClosest(smartCreature, locks); + } + + private static bool TrySetPrimaryLock(SmartCreature smartCreature, Lock targetLock) + { + if (targetLock == null) + { + return false; + } + + smartCreature.SetPrimaryLock(targetLock); + + return true; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs new file mode 100644 index 000000000..dfd2d9f2d --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs @@ -0,0 +1,8 @@ +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public enum IndustrialPrimaryLockSelectionStrategy + { + RichestTile, + MostFertilePlantWithinOptimal, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategyBuilder.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategyBuilder.cs new file mode 100644 index 000000000..21ea59080 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategyBuilder.cs @@ -0,0 +1,26 @@ +using Perpetuum.Collections; + +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public class IndustrialPrimaryLockSelectionStrategyBuilder + { + private readonly WeightedCollection selection; + + public IndustrialPrimaryLockSelectionStrategyBuilder() + { + selection = new WeightedCollection(); + } + + public IndustrialPrimaryLockSelectionStrategyBuilder WithStrategy(IndustrialPrimaryLockSelectionStrategy strategy, int weight = 1) + { + selection.Add(strategy, weight); + + return this; + } + + public IndustrialPrimaryLockSelectionStrategySelector Build() + { + return new IndustrialPrimaryLockSelectionStrategySelector(selection); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategySelector.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategySelector.cs new file mode 100644 index 000000000..513568f65 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategySelector.cs @@ -0,0 +1,27 @@ +using Perpetuum.Collections; +using Perpetuum.Zones.Locking.Locks; + +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public class IndustrialPrimaryLockSelectionStrategySelector + { + private readonly WeightedCollection primaryLockSelectionStrategies; + + public IndustrialPrimaryLockSelectionStrategySelector(WeightedCollection primaryLockSelectionStrategies) + { + this.primaryLockSelectionStrategies = primaryLockSelectionStrategies; + } + + public bool TryUseStrategy(SmartCreature smartCreature, TerrainLock[] locks) + { + var primaryLockSelectionStrategy = primaryLockSelectionStrategies.GetRandom(); + + return IndustrialStrategies.TryInvokeStrategy(primaryLockSelectionStrategy, smartCreature, locks); + } + + public static IndustrialPrimaryLockSelectionStrategyBuilder Create() + { + return new IndustrialPrimaryLockSelectionStrategyBuilder(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs new file mode 100644 index 000000000..dfb4714b6 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs @@ -0,0 +1,63 @@ +using Perpetuum.Zones.Locking.Locks; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public delegate bool IndustrialTargetSelectionStrategy(SmartCreature smartCreature, TerrainLock[] locks); + + public static class IndustrialStrategies + { + public static Dictionary All = + new Dictionary() + { + { IndustrialPrimaryLockSelectionStrategy.RichestTile, TargetRichestTile }, + }; + + public static IndustrialTargetSelectionStrategy GetStrategy(IndustrialPrimaryLockSelectionStrategy strategyType) + { + return All.GetOrDefault(strategyType); + } + + public static bool TryInvokeStrategy(IndustrialPrimaryLockSelectionStrategy strategyType, SmartCreature smartCreature, TerrainLock[] locks) + { + var industrialLockSelectionStrategy = GetStrategy(strategyType); + + if (industrialLockSelectionStrategy == null) + { + return false; + } + + return industrialLockSelectionStrategy(smartCreature, locks); + } + + private static bool TargetRichestTile(SmartCreature smartCreature, TerrainLock[] locks) + { + var industrialTargets = smartCreature.IndustrialValueManager.IndustrialTargets; + + if (!industrialTargets.Any()) + { + return false; + } + + var industrialTargetLocks = locks.Where(u => industrialTargets.Any(h => h.Position.ToString() == u.Location.ToString())); + var mostHostileLock = industrialTargetLocks + .OrderByDescending(u => industrialTargets.FirstOrDefault(h => h.Position.ToString() == u.Location.ToString())?.IndustrialValue ?? 0) + .FirstOrDefault(); + + return TrySetPrimaryLock(smartCreature, mostHostileLock); + } + + private static bool TrySetPrimaryLock(SmartCreature smartCreature, Lock targetLock) + { + if (targetLock == null) + { + return false; + } + + smartCreature.SetPrimaryLock(targetLock); + + return true; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs new file mode 100644 index 000000000..e6c5ab374 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs @@ -0,0 +1,82 @@ +using Perpetuum.Timers; +using Perpetuum.Units; +using System; + +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public class Hostile : IComparable + { + private static readonly TimeSpan _threatTimeOut = TimeSpan.FromSeconds(30); + + private double _threat; + + public readonly Unit Unit; + + public TimeSpan LastThreatUpdate { get; private set; } + + public double Threat + { + get + { + return _threat; + } + private set + { + if (Math.Abs(_threat - value) <= double.Epsilon) + { + return; + } + + _threat = value; + OnThreatUpdated(); + } + } + + public bool IsExpired + { + get + { + return (GlobalTimer.Elapsed - LastThreatUpdate) >= _threatTimeOut; + } + } + + public event Action Updated; + + public Hostile(Unit unit) + { + this.Unit = unit; + Threat = 0.0; + } + + public void AddThreat(Threat threat) + { + if (threat.value <= 0.0) + { + return; + } + + Threat += threat.value; + } + + private void OnThreatUpdated() + { + LastThreatUpdate = GlobalTimer.Elapsed; + Updated?.Invoke(this); + } + + public int CompareTo(Hostile other) + { + if (other._threat < _threat) + { + return -1; + } + + if (other._threat > _threat) + { + return 1; + } + + return 0; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/IPseudoThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/IPseudoThreatManager.cs new file mode 100644 index 000000000..eb7eef928 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/IPseudoThreatManager.cs @@ -0,0 +1,20 @@ +using Perpetuum.Units; +using System; +using System.Collections.Generic; + +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + /// + /// Manager of PseudoThreats + /// Processes and manages an internal collection of players aggressive to an npc + /// but not on the npc's ThreatManager. + /// For awarding players a portion of the total ep reward. + /// + public interface IPseudoThreatManager + { + void Update(TimeSpan time); + void AddOrRefreshExisting(Unit hostile); + void Remove(Unit hostile); + void AwardPseudoThreats(List alreadyAwarded, IZone zone, int ep); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/IThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/IThreatManager.cs new file mode 100644 index 000000000..8d1216418 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/IThreatManager.cs @@ -0,0 +1,18 @@ +using Perpetuum.Units; +using System.Collections.Immutable; + +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public interface IThreatManager + { + bool IsThreatened { get; } + + bool Contains(Unit hostile); + + void Remove(Hostile hostile); + + ImmutableSortedSet Hostiles { get; } + + void Clear(); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreat.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreat.cs new file mode 100644 index 000000000..73f031893 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreat.cs @@ -0,0 +1,37 @@ +using Perpetuum.Units; +using System; + +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + /// + /// An expirable record of a player that is aggressing an npc but the npc is + /// not capable of attacking back (removed from the ThreatManager) + /// + public class PseudoThreat + { + private TimeSpan _lastUpdated = TimeSpan.Zero; + private TimeSpan Expiration = TimeSpan.FromMinutes(1); + + public Unit Unit { get; } + + public bool IsExpired + { + get { return _lastUpdated > Expiration; } + } + + public PseudoThreat(Unit unit) + { + Unit = unit; + } + + public void RefreshThreat() + { + _lastUpdated = TimeSpan.Zero; + } + + public void Update(TimeSpan time) + { + _lastUpdated += time; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs new file mode 100644 index 000000000..07c5ca444 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs @@ -0,0 +1,85 @@ +using Perpetuum.Comparers; +using Perpetuum.Units; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public class PseudoThreatManager : IPseudoThreatManager + { + private readonly List _pseudoThreats; + private readonly object _lock; + + public PseudoThreatManager() + { + _pseudoThreats = new List(); + _lock = new object(); + } + + public bool IsThreatened + { + get { return _pseudoThreats.Any(); } + } + + public void AwardPseudoThreats(List alreadyAwarded, IZone zone, int ep) + { + var pseudoHostileUnits = new List(); + + lock (_lock) + { + pseudoHostileUnits = _pseudoThreats.Select(p => p.Unit).Except(alreadyAwarded, new EntityComparer()).Cast().ToList(); + } + + foreach (var unit in pseudoHostileUnits) + { + var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(unit); + + hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep / 2); + } + } + + public void AddOrRefreshExisting(Unit hostile) + { + lock (_lock) + { + var existing = _pseudoThreats.Where(x => x.Unit == hostile).FirstOrDefault(); + + if (existing != null) + { + existing.RefreshThreat(); + + return; + } + + _pseudoThreats.Add(new PseudoThreat(hostile)); + } + } + + public void Remove(Unit hostile) + { + lock (_lock) + { + _pseudoThreats.RemoveAll(x => x.Unit == hostile); + } + } + + public void Update(TimeSpan time) + { + lock (_lock) + { + foreach (var threat in _pseudoThreats) + { + threat.Update(time); + } + + CleanExpiredThreats(); + } + } + + private void CleanExpiredThreats() + { + _pseudoThreats.RemoveAll(threat => threat.IsExpired); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Threat.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Threat.cs new file mode 100644 index 000000000..96ece33f3 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Threat.cs @@ -0,0 +1,33 @@ +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public struct Threat + { + + public const double WEBBER = 25; + public const double LOCK_PRIMARY = 2.0; + public const double LOCK_SECONDARY = 1.0; + public const double SENSOR_DAMPENER = 25.0; + public const double BODY_PULL = 1.0; + public const double SENSOR_BOOSTER = 15; + public const double REMOTE_SENSOR_BOOSTER = 15; + + public readonly ThreatType type; + public readonly double value; + + public Threat(ThreatType type, double value) + { + this.type = type; + this.value = value; + } + + public override string ToString() + { + return $"{type} = {value}"; + } + + public static Threat Multiply(Threat threat, double multiplier) + { + return new Threat(threat.type, threat.value * multiplier); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatExtensions.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatExtensions.cs new file mode 100644 index 000000000..d7534d7d5 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatExtensions.cs @@ -0,0 +1,11 @@ +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public static class ThreatExtensions + { + [CanBeNull] + public static Hostile GetMostHatedHostile(this IThreatManager manager) + { + return manager.Hostiles.Min; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs new file mode 100644 index 000000000..f7b134fe8 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs @@ -0,0 +1,72 @@ +using Perpetuum.Units; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public class ThreatManager : IThreatManager + { + private ImmutableDictionary _hostiles = ImmutableDictionary.Empty; + + public ImmutableSortedSet Hostiles + { + get { return _hostiles.Values.ToImmutableSortedSet(); } + } + + public bool IsThreatened + { + get { return !_hostiles.IsEmpty; } + } + + public Hostile GetOrAddHostile(Unit unit) + { + return ImmutableInterlocked.GetOrAdd(ref _hostiles, unit.Eid, eid => + { + var h = new Hostile(unit); + + return h; + }); + } + + public bool Contains(Unit unit) + { + return _hostiles.ContainsKey(unit.Eid); + } + + public void Clear() + { + _hostiles.Clear(); + } + + public void Remove(Hostile hostile) + { + ImmutableInterlocked.TryRemove(ref _hostiles, hostile.Unit.Eid, out hostile); + } + + public string ToDebugString() + { + if (_hostiles.Count == 0) + { + return string.Empty; + } + + var sb = new StringBuilder(); + + sb.AppendLine(); + sb.AppendLine("========== THREAT =========="); + sb.AppendLine(); + + foreach (var hostile in _hostiles.Values.OrderByDescending(h => h.Threat)) + { + sb.AppendFormat(" {0} ({1}) => {2}", hostile.Unit.ED.Name, hostile.Unit.Eid, hostile.Threat); + sb.AppendLine(); + } + + sb.AppendLine(); + sb.AppendLine("============================"); + + return sb.ToString(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatType.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatType.cs new file mode 100644 index 000000000..b7d264ea4 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatType.cs @@ -0,0 +1,16 @@ +namespace Perpetuum.Zones.NpcSystem.ThreatManaging +{ + public enum ThreatType + { + Undefined, + Bodypull, + Damage, + Support, + Lock, + Buff, + Debuff, + Direct, + EnWar, + Ewar, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs deleted file mode 100644 index e21955f00..000000000 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using Perpetuum.Timers; -using Perpetuum.Units; - -namespace Perpetuum.Zones.NpcSystem -{ - public enum ThreatType - { - Undefined, - Bodypull, - Damage, - Support, - Lock, - Buff, - Debuff, - Direct - } - - public struct Threat - { - - public const double WEBBER = 25; - public const double LOCK_PRIMARY = 2.0; - public const double LOCK_SECONDARY = 1.0; - public const double SENSOR_DAMPENER = 25.0; - public const double BODY_PULL = 1.0; - public const double SENSOR_BOOSTER = 15; - public const double REMOTE_SENSOR_BOOSTER = 15; - - public readonly ThreatType type; - public readonly double value; - - public Threat(ThreatType type, double value) - { - this.type = type; - this.value = value; - } - - public override string ToString() - { - return $"{type} = {value}"; - } - - public static Threat Multiply(Threat threat, double multiplier) - { - return new Threat(threat.type,threat.value * multiplier); - } - } - - public class Hostile : IComparable - { - private static readonly TimeSpan _threatTimeOut = TimeSpan.FromSeconds(30); - - private double _threat; - - public readonly Unit unit; - - public TimeSpan LastThreatUpdate { get; private set; } - - public event Action Updated; - - public Hostile(Unit unit) - { - this.unit = unit; - Threat = 0.0; - } - - public void AddThreat(Threat threat) - { - if ( threat.value <= 0.0 ) - return; - - Threat += threat.value; - } - - public bool IsExpired - { - get { return (GlobalTimer.Elapsed - LastThreatUpdate) >= _threatTimeOut; } - } - - public double Threat - { - get { return _threat; } - private set - { - if (Math.Abs(_threat - value) <= double.Epsilon) - return; - - _threat = value; - - OnThreatUpdated(); - } - } - - private void OnThreatUpdated() - { - LastThreatUpdate = GlobalTimer.Elapsed; - - Updated?.Invoke(this); - } - - public int CompareTo(Hostile other) - { - if (other._threat < _threat) - return -1; - - if (other._threat > _threat) - return 1; - - return 0; - } - } - - public interface IThreatManager - { - bool Contains(Unit hostile); - void Remove(Hostile hostile); - ImmutableSortedSet Hostiles { get; } - } - - public static class ThreatExtensions - { - [CanBeNull] - public static Hostile GetMostHatedHostile(this IThreatManager manager) - { - return manager.Hostiles.Min; - } - } - - public class ThreatManager : IThreatManager - { - private ImmutableSortedSet _sortedHotiles = ImmutableSortedSet.Empty; - private ImmutableDictionary _hostiles = ImmutableDictionary.Empty; - private bool _isDirty; - - public Hostile GetHostile(Unit unit) - { - return ImmutableInterlocked.GetOrAdd(ref _hostiles, unit.Eid, eid => - { - var h = new Hostile(unit); - h.Updated += OnHostileThreatUpdated; - _isDirty = true; - return h; - }); - } - - private void OnHostileThreatUpdated(Hostile hostile) - { - _isDirty = true; - } - - public ImmutableSortedSet Hostiles - { - get { return _sortedHotiles; } - } - - public bool Contains(Unit unit) - { - return _hostiles.ContainsKey(unit.Eid); - } - - public void Clear() - { - _hostiles.Clear(); - _isDirty = true; - } - - public void Remove(Hostile hostile) - { - if (ImmutableInterlocked.TryRemove(ref _hostiles, hostile.unit.Eid, out hostile)) - _isDirty = true; - } - - public void Update() - { - if (!_isDirty) - return; - - _isDirty = false; - - _sortedHotiles = _hostiles.Values.ToImmutableSortedSet(); - } - - public string ToDebugString() - { - if ( _hostiles.Count == 0 ) - return string.Empty; - - var sb = new StringBuilder(); - - sb.AppendLine(); - sb.AppendLine("========== THREAT =========="); - sb.AppendLine(); - - foreach (var hostile in _hostiles.Values.OrderByDescending(h => h.Threat)) - { - sb.AppendFormat(" {0} ({1}) => {2}", hostile.unit.ED.Name,hostile.unit.Eid, hostile.Threat); - sb.AppendLine(); - } - - sb.AppendLine(); - sb.AppendLine("============================"); - - return sb.ToString(); - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 0d768696a..c04c9e025 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -1,253 +1,40 @@ using System; -using System.Collections.Generic; -using System.Linq; using Perpetuum.EntityFramework; -using Perpetuum.Modules; -using Perpetuum.Modules.EffectModules; -using Perpetuum.Modules.Weapons; using Perpetuum.Players; using Perpetuum.StateMachines; -using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.Locking; -using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.Terrains; +using Perpetuum.Zones.NpcSystem.AI; namespace Perpetuum.Zones.NpcSystem { - public class ModuleActivator : IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor + public class Turret : Creature { - private readonly IntervalTimer _timer; - private readonly ActiveModule _module; - - public ModuleActivator(ActiveModule module) - { - _module = module; - _timer = new IntervalTimer(TimeSpan.FromSeconds(1),true); - } - - public void Update(TimeSpan time) - { - _timer.Update(time); - - if (!_timer.Passed) - return; - - _timer.Reset(); - - if (_module.State.Type != ModuleStateType.Idle) - return; - - _module.AcceptVisitor(this); - } - - private void TryActiveModule(LOSResult result, UnitLock primaryLock) - { - if (result.hit && !result.blockingFlags.HasFlag(BlockingFlags.Plant)) - return; - - _module.Lock = primaryLock; - _module.State.SwitchTo(ModuleStateType.Oneshot); - } - - public void Visit(MissileWeaponModule module) - { - var hasShieldEffect = module.ParentRobot.HasShieldEffect; - if (hasShieldEffect) - return; - - var primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); - if (primaryLock == null) - return; - - var visibility = module.ParentRobot.GetVisibility(primaryLock.Target); - if (visibility == null) - return; - - var result = visibility.GetLineOfSight(true); - TryActiveModule(result, primaryLock); - } - - public void Visit(WeaponModule module) - { - var hasShieldEffect = module.ParentRobot.HasShieldEffect; - if (hasShieldEffect) - return; - - var primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); - if (primaryLock == null) - return; - - var visibility = module.ParentRobot.GetVisibility(primaryLock.Target); - if (visibility == null) - return; - - var result = visibility.GetLineOfSight(false); - TryActiveModule(result,primaryLock); - } - - private const double ARMOR_REPAIR_THRESHOLD = 0.8; - - public void Visit(ArmorRepairModule module) - { - if (module.ParentRobot.ArmorPercentage >= ARMOR_REPAIR_THRESHOLD) - return; - - module.State.SwitchTo(ModuleStateType.Oneshot); - } - - private const double SHIELD_ARMOR_THRESHOLD = 0.3; - - public void Visit(ShieldGeneratorModule module) - { - if (module.ParentRobot.ArmorPercentage >= SHIELD_ARMOR_THRESHOLD) - return; - - module.State.SwitchTo(ModuleStateType.Oneshot); - } - - private const double SENSOR_JAMMER_CORE_THRESHOLD = 0.5; - - public void Visit(SensorJammerModule module) - { - if ( module.ParentRobot.CorePercentage < SENSOR_JAMMER_CORE_THRESHOLD ) - return; - - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); - if (lockTarget == null) - return; - - module.Lock = lockTarget; - module.State.SwitchTo(ModuleStateType.Oneshot); - } - - private const double SENSOR_DAMPENER_CORE_THRESHOLD = 0.5; - - public void Visit(SensorDampenerModule module) - { - if (module.ParentRobot.CorePercentage < SENSOR_DAMPENER_CORE_THRESHOLD) - return; - - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); - if (lockTarget == null) - return; - - module.Lock = lockTarget; - module.State.SwitchTo(ModuleStateType.Oneshot); - } - - private const double WEBBER_CORE_THRESHOLD = 0.5; - - public void Visit(WebberModule module) - { - if (module.ParentRobot.CorePercentage < WEBBER_CORE_THRESHOLD) - return; - - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); - if (lockTarget == null) - return; - - module.Lock = lockTarget; - module.State.SwitchTo(ModuleStateType.Oneshot); - } - - private const double ENERGY_NEUTRALIZER_CORE_THRESHOLD = 0.5; - - public void Visit(EnergyNeutralizerModule module) - { - if (module.ParentRobot.CorePercentage < ENERGY_NEUTRALIZER_CORE_THRESHOLD) - return; - - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); - if ( lockTarget == null ) - return; - - var visibility = module.ParentRobot.GetVisibility(lockTarget.Target); - if (visibility == null) - return; - - var r = visibility.GetLineOfSight(false); - if (r != null) - { - if (r.hit) - return; - } - - module.Lock = lockTarget; - module.State.SwitchTo(ModuleStateType.Oneshot); - } - - private const double ENERGY_VAMPIRE_CORE_THRESHOLD = 0.8; - - public void Visit(EnergyVampireModule module) - { - if ( module.ParentRobot.CorePercentage >= ENERGY_VAMPIRE_CORE_THRESHOLD ) - return; - - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); - if ( lockTarget == null ) - return; - - var visibility = module.ParentRobot.GetVisibility(lockTarget.Target); - if (visibility == null) - return; - - var r = visibility.GetLineOfSight(false); - if (r != null) - { - if (r.hit) - return; - } - - module.Lock = lockTarget; - module.State.SwitchTo(ModuleStateType.Oneshot); - } + private readonly NullAI _nullAI; + private readonly FiniteStateMachine _aiStateMachine; - public void Visit(SensorBoosterModule module) + protected TurretAI AI { - if (module.State.Type == ModuleStateType.Idle) - { - module.State.SwitchTo(ModuleStateType.AutoRepeat); - } + get { return _aiStateMachine.Current ?? _nullAI; } } - public void Visit(ArmorHardenerModule module) + public FiniteStateMachine AIStateMachine { - if (module.State.Type == ModuleStateType.Idle) - { - module.State.SwitchTo(ModuleStateType.AutoRepeat); - } + get { return _aiStateMachine; } } - } - - - - public class Turret : Creature - { - private readonly NullAI _nullAI; - private readonly FiniteStateMachine _ai; - protected Turret() { _nullAI = new NullAI(this); - _ai = new FiniteStateMachine(); + _aiStateMachine = new FiniteStateMachine(); } - protected TurretAI AI + public override void AcceptVisitor(IEntityVisitor visitor) { - get { return _ai.Current ?? _nullAI; } + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) @@ -271,222 +58,30 @@ protected override void OnUnitEffectChanged(Unit unit, Effect effect, bool apply base.OnUnitEffectChanged(unit, effect, apply); } - public override void AcceptVisitor(IEntityVisitor visitor) - { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); - } - protected override void OnUpdate(TimeSpan time) { - _ai.Update(time); + _aiStateMachine.Update(time); base.OnUpdate(time); } - protected void LockHostile(Unit unit,bool force = false) + public void LockHostile(Unit unit,bool force = false) { if (IsLocked(unit)) + { return; + } if (!force && !IsHostile(unit)) + { return; + } AddLock(unit, false); } - internal override bool IsHostile(Player player) + public override bool IsHostile(Player player) { return true; } - - protected abstract class TurretAI : IState - { - private readonly Turret _turret; - - protected TurretAI(Turret turret) - { - _turret = turret; - } - - public virtual void Enter() - { - WriteLog("Turret AI:" + GetType().Name); - } - - public virtual void Exit() - { - WriteLog("Turret AI:" + GetType().Name); - } - - public virtual void Update(TimeSpan time) - { - - } - - protected void WriteLog(string message) - { -// Logger.Info(message); - } - - public virtual void ToInactiveAI() - { - _turret._ai.ChangeState(new InactiveAI(_turret)); - } - - public virtual void ToActiveAI() - { - _turret._ai.ChangeState(new ActiveAI(_turret)); - } - - public virtual void AttackHostile(Unit unit) - { - - } - } - - private class NullAI : TurretAI - { - public NullAI(Turret turret) : base(turret) - { - } - } - - private class InactiveAI : TurretAI - { - private readonly Turret _turret; - - public InactiveAI(Turret turret) : base(turret) - { - _turret = turret; - } - - public override void Enter() - { - _turret.StopAllModules(); - _turret.ResetLocks(); - base.Enter(); - } - - public override void ToInactiveAI() - { - // nem csinal semmit - } - } - - private class ActiveAI : TurretAI - { - private readonly Turret _turret; - - private readonly List _moduleActivators; - - public ActiveAI(Turret turret) : base(turret) - { - _turret = turret; - - _moduleActivators = new List(); - - _minCycleTime = TimeSpan.FromSeconds(5); - - foreach (var module in _turret.ActiveModules) - { - _moduleActivators.Add(new ModuleActivator(module)); - _minCycleTime = _minCycleTime.Min(module.CycleTime); - } - } - - public override void Enter() - { - foreach (var unitVisibility in _turret.GetVisibleUnits()) - { - _turret.LockHostile(unitVisibility.Target); - } - - base.Enter(); - } - - private readonly TimeSpan _minCycleTime; - - public override void Exit() - { - _turret.StopAllModules(); - _turret.ResetLocks(); - base.Exit(); - } - - public override void AttackHostile(Unit unit) - { - if (!_turret.IsHostile(unit)) - return; - - _turret.LockHostile(unit); - base.AttackHostile(unit); - } - - private readonly IntervalTimer _primarySelectTimer = new IntervalTimer(0); - - public override void Update(TimeSpan time) - { - if ( !SelectPrimaryTarget(time) ) - return; - - foreach (var activator in _moduleActivators) - { - activator.Update(time); - } - - base.Update(time); - } - - private bool SelectPrimaryTarget(TimeSpan time) - { - var locks = _turret.GetLocks().Where(l => l.State == LockState.Locked).ToArray(); - if (locks.Length <= 0) - return false; - - _primarySelectTimer.Update(time); - - if (_primarySelectTimer.Passed) - { - _primarySelectTimer.Interval = FastRandom.NextTimeSpan(_minCycleTime); - - var validLocks = new List(); - - foreach (var l in locks) - { - var unitLock = (UnitLock)l; - - if (unitLock.Primary) - continue; - - var visibility = _turret.GetVisibility(unitLock.Target); - if (visibility == null) - continue; - - var r = visibility.GetLineOfSight(false); - if (r != null) - { - if (r.hit && (r.blockingFlags & BlockingFlags.Plant) == 0) - continue; - } - - validLocks.Add(unitLock); - } - - if (validLocks.Count > 0) - { - var newPrimary = validLocks.RandomElement(); - _turret.SetPrimaryLock(newPrimary); - return true; - } - } - - return locks.Any(l => l.Primary); - } - - public override void ToActiveAI() - { - // nem csinal semmit - } - } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/PBS/ConstructionAmmoHelpers.cs b/src/Perpetuum/Zones/PBS/ConstructionAmmoHelpers.cs new file mode 100644 index 000000000..3529f0298 --- /dev/null +++ b/src/Perpetuum/Zones/PBS/ConstructionAmmoHelpers.cs @@ -0,0 +1,74 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Services.Looting; +using System; +using System.Collections.Generic; + +namespace Perpetuum.Zones.PBS +{ + public static class ConstructionAmmoHelpers + { + private static readonly Lazy> _constructionBlocksByLevel = new Lazy>(() => + { + return new Dictionary() + { + {0, EntityDefault.GetByName(DefinitionNames.CONSTRUCTION_MODULE_AMMO_T0) }, + {1, EntityDefault.GetByName(DefinitionNames.CONSTRUCTION_MODULE_AMMO_T1) }, + {2, EntityDefault.GetByName(DefinitionNames.CONSTRUCTION_MODULE_AMMO_T2) }, + {3, EntityDefault.GetByName(DefinitionNames.CONSTRUCTION_MODULE_AMMO_T3) }, + }; + }); + + private static EntityDefault GetByLevel(int level) + { + return _constructionBlocksByLevel.Value.GetOrDefault(level, EntityDefault.None); + } + + public static int GetByTargetDefinition(EntityDefault structureDefinition) + { + return GetByLevel(structureDefinition.Tier.level).Definition; + } + + // Loot functions + private const double DROP_RATE = 0.5; + + public static IEnumerable GetConstructionAmmoLootOnDead(IPBSObject pbsObject) + { + var amount = ComputeAmountOnDead(pbsObject); + var constructionLootList = new List(); + if (amount > 0) + { + constructionLootList.Add(BuildConstructionAmmoLoot(pbsObject, amount)); + } + return constructionLootList; + } + + public static IEnumerable GetConstructionAmmoLootOnDeconstruct(IPBSObject pbsObject) + { + var amount = ComputeAmountOnDeconstruct(pbsObject); + var constructionLootList = new List(); + if (amount > 0) + { + constructionLootList.Add(BuildConstructionAmmoLoot(pbsObject, amount)); + } + return constructionLootList; + } + + private static int ComputeAmountOnDeconstruct(IPBSObject pbsObject) + { + return (int)(pbsObject.ConstructionLevelMax * DROP_RATE); + } + + private static int ComputeAmountOnDead(IPBSObject pbsObject) + { + var constructionLevelMax = pbsObject.ConstructionLevelMax.Max(1); + return (int)(constructionLevelMax * DROP_RATE * pbsObject.ConstructionLevelCurrent / constructionLevelMax); + } + + private static LootItem BuildConstructionAmmoLoot(IPBSObject pbsObject, int amount) + { + var ammoDef = GetByTargetDefinition(pbsObject.ED); + return LootItemBuilder.Create(ammoDef).SetQuantity(amount).Build(); + } + } +} diff --git a/src/Perpetuum/Zones/PBS/ConstructionModule.cs b/src/Perpetuum/Zones/PBS/ConstructionModule.cs index ce4b00ba2..c68a4826e 100644 --- a/src/Perpetuum/Zones/PBS/ConstructionModule.cs +++ b/src/Perpetuum/Zones/PBS/ConstructionModule.cs @@ -33,7 +33,7 @@ private void DoConstruct() var constructionAmount = ammo.GetPropertyModifier(AggregateField.construction_charge_amount); - if (constructionAmount.Value < 0) + if (constructionAmount.Value < 0) // Deconstruction (negative amount) { //deconstructhoz access kell pbsObject.CheckAccessAndThrowIfFailed(ParentRobot.GetCharacter()); @@ -45,9 +45,14 @@ private void DoConstruct() pbsObject.OnlineStatus.ThrowIfTrue(ErrorCodes.NotPossibleOnOnlineNode); } + else // Construction + { + var techLevel = pbsObject.ED.Tier.level; + var constructionLevel = ammo.GetPropertyModifier(AggregateField.construction_charge_techmax); + techLevel.ThrowIfGreater((int)constructionLevel.Value, ErrorCodes.TechLevelTooLow); + } pbsObject.ModifyConstructionLevel((int) constructionAmount.Value).ThrowIfError(); - CreateBeam(unitLock.Target, BeamState.AlignToTerrain); } diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs new file mode 100644 index 000000000..8adb00bc9 --- /dev/null +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -0,0 +1,168 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Common; +using Perpetuum.Data; +using Perpetuum.Groups.Corporations; +using Perpetuum.Items.Templates; +using Perpetuum.Services.Channels; +using Perpetuum.Services.Mail; +using Perpetuum.Services.MarketEngine; +using Perpetuum.Services.Sparks.Teleports; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.PBS.DockingBases +{ + /// + /// This is a special PBS base that expires based on a configured lifetime + /// It can not be deconstructed, it must expire or be killed + /// There are also special rules about placement which are enforced in the PBSDeployer module + /// The expiration status of the base is communicate to players by mail and terminal chat messages and MOTD + /// + public class ExpiringPBSDockingBase : PBSDockingBase + { + private IUnitDespawnHelper _despawnHelper; + private readonly TimeSpan _minLifeOnInit = TimeSpan.FromMinutes(10); + private bool _firstUpdate = true; + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; + private readonly Character _announcer; + + public ExpiringPBSDockingBase( + MarketHelper marketHelper, + ICorporationManager corporationManager, + IChannelManager channelManager, + ICentralBank centralBank, + IRobotTemplateRelations robotTemplateRelations, + DockingBaseHelper dockingBaseHelper, + SparkTeleportHelper sparkTeleportHelper, + PBSObjectHelper.Factory pbsObjectHelperFactory) : base(marketHelper, + corporationManager, + channelManager, + centralBank, + robotTemplateRelations, + dockingBaseHelper, + sparkTeleportHelper, + pbsObjectHelperFactory) + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + } + + public TimeSpan LifeTime + { + get + { + return TimeSpan.FromHours(ED.Config.lifeTime ?? 72); + } + } + + public DateTime EndTime + { + get + { + return DynamicProperties.GetOrAdd(k.endTime, () => DateTime.Now + LifeTime); + } + } + + public TimeSpan Remaining + { + get + { + return (EndTime - DateTime.Now).Max(_minLifeOnInit); + } + } + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + _despawnHelper = UnitDespawnHelper.Create(this, Remaining); + _despawnHelper.DespawnStrategy = (unit) => + { + ReinforceHandler.ReinforceCounter = 0; + ReinforceHandler.CurrentState.ToVulnerable(); + Kill(); + }; + _firstUpdate = true; + + base.OnEnterZone(zone, enterType); + } + + private void OnFirst() + { + _firstUpdate = false; + ChannelManager.JoinChannel(ChannelName, _announcer, ChannelMemberRole.Operator, null); + ChannelManager.SetTopic(ChannelName, _announcer, $"Base Expires at {EndTime.ToString("F")}"); + SendMailStatusAsync(); + } + + protected override void OnUpdate(TimeSpan time) + { + if (_firstUpdate) + OnFirst(); + + _despawnHelper?.Update(time, this); + + base.OnUpdate(time); + } + + protected override void JoinChannel(Character character) + { + base.JoinChannel(character); + ChannelManager.Announcement(ChannelName, _announcer, $"Base is going to expire in: {Remaining.ToHumanTimeString()}"); + } + + public override ErrorCodes IsDeconstructAllowed() + { + return ErrorCodes.DockingBaseNotSetToDeconstruct; + } + + public override ErrorCodes SetDeconstructionRight(Character issuer, bool state) + { + DynamicProperties.Remove(k.allowDeconstruction); + return ErrorCodes.DockingBaseNotSetToDeconstruct; + } + + /// + /// Get authorized managers of the facility + /// + /// IEnumerable + private IEnumerable AuthorizedCorpOfficers() + { + var corp = Corporation.Get(Owner); + if (corp == null) + return new Character[] { }; + + return corp.GetMembersWithAnyRoles(CorporationRole.DeputyCEO, + CorporationRole.CEO, + CorporationRole.editPBS).Select(m => m.character); + } + + /// + /// Send mail to the top 10 most active authorized corp officers + /// + /// Task + private Task SendMailStatusAsync() + { + return Task.Run(() => + { + var officers = AuthorizedCorpOfficers().OrderByDescending(c => c.LastLogout).Take(10); + var message = BuildStatusMessage(); + var subject = "Syntec Base Expiration info"; + using (var scope = Db.CreateTransaction()) + { + foreach (var officer in officers) + { + MailHandler.SendMail(_announcer, officer, subject, BuildStatusMessage(), MailType.storyteller, out _, out _); + } + scope.Complete(); + } + }); + } + + private string BuildStatusMessage() + { + return $"Your Syntec Base will expire at {EndTime.ToString("F")} in {Remaining.ToHumanTimeString()}.\n\nBase will become unstable and self-destruct at this time!\nPlease plan your stay accordingly."; + } + } +} diff --git a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index 95eb9c3f7..bad6e7b24 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs @@ -31,7 +31,7 @@ public class PBSDockingBase : DockingBase, IPBSObject, IStandingController private readonly ICorporationManager _corporationManager; private readonly SparkTeleportHelper _sparkTeleportHelper; private readonly PBSStandingController _standingController; - private readonly PBSObjectHelper _pbsObjectHelper; + protected readonly PBSObjectHelper _pbsObjectHelper; private readonly PBSReinforceHandler _pbsReinforceHandler; private readonly PBSTerritorialVisibilityHelper _pbsTerritorialVisibilityHelper; @@ -49,6 +49,8 @@ public PBSDockingBase(MarketHelper marketHelper,ICorporationManager corporationM public IPBSReinforceHandler ReinforceHandler => _pbsReinforceHandler; public IPBSConnectionHandler ConnectionHandler => _pbsObjectHelper.ConnectionHandler; + public int ZoneIdCached { get; private set; } + public ErrorCodes ModifyConstructionLevel(int amount, bool force = false) { return _pbsObjectHelper.ModifyConstructionLevel(amount, force); @@ -140,7 +142,10 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) _pbsReinforceHandler.Init(); _pbsTerritorialVisibilityHelper.Init(); - ClearChildren(); //ez azert kell, hogy a zonan ne legyenek gyerekei semmikepp + ZoneIdCached = zone.Id; //OPP: make sure this is set! + + //OPP: The following line is commented because it broke the BaseListFacilities command. + // ClearChildren(); //ez azert kell, hogy a zonan ne legyenek gyerekei semmikepp Parent = 0; //ez azert kell, hogy a bazison levo kontenerek megtalaljak, mint root base.OnEnterZone(zone, enterType); } @@ -148,9 +153,9 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) public override void OnLoadFromDb() { base.OnLoadFromDb(); - - _pbsObjectHelper.Init(); + _pbsObjectHelper.Init(); + _pbsTerritorialVisibilityHelper.Init(); } @@ -219,7 +224,7 @@ public override void OnDeleteFromDb() //NO BASE CLASS CALL -> szandekos Logger.DebugInfo($"[{InfoString}] docking base on delete"); Logger.DebugInfo($"[{InfoString}] zonaid jo, helperes cucc jon"); - PBSHelper.DeletePBSDockingBase(Zone,this).ThrowIfError(); + PBSHelper.DeletePBSDockingBase(ZoneIdCached, this).ThrowIfError(); } protected override void OnRemovedFromZone(IZone zone) @@ -231,9 +236,6 @@ protected override void OnRemovedFromZone(IZone zone) PBSHelper.SendPBSDockingBaseDeleteToProduction(Eid); base.OnRemovedFromZone(zone); - - //NO delete from db, reparent to trash - PBSHelper.DeletePBSDockingBase(zone, this); } protected override void OnDead(Unit killer) @@ -307,7 +309,6 @@ public Dictionary GetTerritorialDictionary() var ctd = GenerateTerritoryDictionary(); _cacheTerritoryDictionary = ctd; - } return _cacheTerritoryDictionary; @@ -335,7 +336,7 @@ private Dictionary GenerateTerritoryDictionary() return info; } - public ErrorCodes SetDeconstructionRight(Character issuer, bool state) + public virtual ErrorCodes SetDeconstructionRight(Character issuer, bool state) { this.CheckAccessAndThrowIfFailed(issuer); @@ -369,7 +370,7 @@ public ErrorCodes SetDeconstructionRight(Character issuer, bool state) /// If property is present then it's set to deconstruct /// /// - public ErrorCodes IsDeconstructAllowed() + public virtual ErrorCodes IsDeconstructAllowed() { if (DynamicProperties.Contains(k.allowDeconstruction)) @@ -457,6 +458,26 @@ public override bool IsOnGammaZone() return true; } + public override bool IsVisible(Character character) + { + if (DockingBaseMapVisibility == PBSDockingBaseVisibility.open) + return true; + + Corporation.GetCorporationEidAndRoleFromSql(character, out long corporationEid, out CorporationRole role); + if (Owner == corporationEid) + { + if (DockingBaseMapVisibility == PBSDockingBaseVisibility.corporation) + { + return true; + } + else if (DockingBaseMapVisibility == PBSDockingBaseVisibility.hidden) + { + return role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO, CorporationRole.viewPBS); + } + } + return false; + } + private bool _trashWasKilled; public void TrashMe() { @@ -483,7 +504,7 @@ public int GetNetworkNodeRange() return 0; } - public ErrorCodes DoCleanUpWork(IZone zone) + public ErrorCodes DoCleanUpWork(int zone) { var ec = ErrorCodes.NoError; diff --git a/src/Perpetuum/Zones/PBS/PBSDeployer.cs b/src/Perpetuum/Zones/PBS/PBSDeployer.cs index 58e7e0666..9c326c7d3 100644 --- a/src/Perpetuum/Zones/PBS/PBSDeployer.cs +++ b/src/Perpetuum/Zones/PBS/PBSDeployer.cs @@ -3,6 +3,7 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Players; using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; namespace Perpetuum.Zones.PBS { @@ -34,6 +35,9 @@ protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, //check zone for conditions PBSHelper.CheckZoneForDeployment(zone, spawnPosition,pbsEd).ThrowIfError(); + //Check distance to NPC Base spawns + NPCBasePresenceUtils.WithinRangeOfNPCBase(zone, spawnPosition).ThrowIfTrue(ErrorCodes.TooCloseToNPCBase); + //pass owner pbsEgg.DeployerPlayer = player; diff --git a/src/Perpetuum/Zones/PBS/PBSEgg.cs b/src/Perpetuum/Zones/PBS/PBSEgg.cs index b8734a7f3..442577bb3 100644 --- a/src/Perpetuum/Zones/PBS/PBSEgg.cs +++ b/src/Perpetuum/Zones/PBS/PBSEgg.cs @@ -153,11 +153,21 @@ public void CheckDefinitionRelatedConditionsOrThrow(IZone zone, Position spawnPo throw new PerpetuumException(ErrorCodes.ConsistencyError); } - + // Check zone level (Gamma Tier) + (pbsNodeEntityDefault.Tier.level > zone.Configuration.PBSTechLimit).ThrowIfTrue(ErrorCodes.PBSTechLevelTooHighForZone); + + // For bases if (pbsNodeEntityDefault.CategoryFlags.IsCategory(CategoryFlags.cf_pbs_docking_base)) { - //check deploying of bases - PBSHelper.ValidatePBSDockingBasePlacement(zone, spawnPosition, owner, pbsNodeEntityDefault).ThrowIfError(); + // Special "Expiring" base + if (pbsNodeEntityDefault.Name == DefinitionNames.PBS_EXPIRING_DOCKING_BASE) + { + PBSHelper.ValidateExpiringPBSDockingBasePlacement(zone, spawnPosition, owner, pbsNodeEntityDefault).ThrowIfError(); + } + else // Normal PBS Docking base + { + PBSHelper.ValidatePBSDockingBasePlacement(zone, spawnPosition, owner, pbsNodeEntityDefault).ThrowIfError(); + } return; } @@ -170,7 +180,6 @@ public void CheckDefinitionRelatedConditionsOrThrow(IZone zone, Position spawnPo } //itt meg lehet mast is megnezni egyelore nem kellett - } public EntityDefault TargetPBSNodeDefault diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 6802915bd..a9c3cbe1b 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -39,7 +39,8 @@ public static class PBSHelper public static ItemDeployerHelper ItemDeployerHelper { get; set; } private const int MAX_BASES_PER_CORP_PER_ZONE = 3; - + private const int MAX_EXPIRING_BASES_PER_ZONE = 24; + private const int MAX_EXPIRING_BASES_PER_ZONE_PER_CORP = 3; public static bool IsOfflineOnReinforce(Unit pbsUnit) { @@ -112,44 +113,85 @@ public static int GetCapsuleDefinitionByPBSObjectDefinition(int nodeDefinition) } - public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position position, long owner, - EntityDefault dockingbasEntityDefault) + public static ErrorCodes ValidateExpiringPBSDockingBasePlacement(IZone zone, Position position, long owner, EntityDefault definition) { - //only a set number of bases for one corporation - var baseCountPerCorporation = zone.Units.Count(u => u is PBSDockingBase && u.Owner == owner); + var expiringBasesAndEggs = zone.Units.Where(IsExpiringBaseOrEgg); - if (baseCountPerCorporation + 1 > MAX_BASES_PER_CORP_PER_ZONE) + if (expiringBasesAndEggs.Count() >= MAX_EXPIRING_BASES_PER_ZONE) { - return ErrorCodes.MaxDockingBasePerZonePerCorporationReached; + return ErrorCodes.MaxExpiringBasePerZoneReached; } + else if (expiringBasesAndEggs.Count(u => IsUnitOrEggOwnedByCorp(u, owner)) >= MAX_EXPIRING_BASES_PER_ZONE_PER_CORP) + { + return ErrorCodes.MaxExpiringBasePerZonePerCorpReached; + } + else if (zone.GetTeleportColumns().Any(t => position.TotalDistance2D(t.CurrentPosition) < DistanceConstants.PBS_DIST_FROM_TELEPORT)) + { + return ErrorCodes.TeleportIsTooClose; + } + return CheckRangeToOtherBases(zone, position, definition); + } - //current total number of bases per zone - var baseCountPerZone = zone.Units.Count(u => u is PBSDockingBase); - + public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position position, long owner, + EntityDefault definition) + { + //Get PBS docking bases (or eggs) - excluding Expiring types + var bases = zone.Units.Where(u => IsBaseOrEgg(u) && !IsExpiringBaseOrEgg(u)); + //Total count + var baseCountPerZone = bases.Count(); //the zone allows var maxBasesPerZone = zone.Configuration.MaxDockingBase; - if (baseCountPerZone + 1 > maxBasesPerZone) + if (bases.Count() >= maxBasesPerZone) { return ErrorCodes.MaxDockingBasePerZoneReached; } + else if (maxBasesPerZone > MAX_BASES_PER_CORP_PER_ZONE) + { + //only a set number of bases for one corporation + if (bases.Count(u => IsUnitOrEggOwnedByCorp(u, owner)) >= MAX_BASES_PER_CORP_PER_ZONE) + { + return ErrorCodes.MaxDockingBasePerZonePerCorporationReached; + } + } - var typeExclusiveRange = dockingbasEntityDefault.Config.typeExclusiveRange; + return CheckRangeToOtherBases(zone, position, definition); + } + + private static bool IsBaseOrEgg(Unit unit) + { + return unit.IsCategory(CategoryFlags.cf_pbs_docking_base) || unit.IsCategory(CategoryFlags.cf_pbs_docking_base_capsules); + } + + private static bool IsExpiringBaseOrEgg(Unit unit) + { + return unit is ExpiringPBSDockingBase || (unit as PBSEgg)?.TargetPBSNodeDefault.Name == DefinitionNames.PBS_EXPIRING_DOCKING_BASE; + } + + private static bool IsUnitOrEggOwnedByCorp(Unit unit, long corpEid) + { + if (unit is PBSEgg egg) + { + return egg.DeployerPlayer.CorporationEid == corpEid; + } + return unit.Owner == corpEid; + } + + private static ErrorCodes CheckRangeToOtherBases(IZone zone, Position position, EntityDefault definition) + { + var typeExclusiveRange = definition.Config.typeExclusiveRange; if (typeExclusiveRange == null) { - Logger.Error("no typeExclusiveRange defined for " + dockingbasEntityDefault); + Logger.Error("no typeExclusiveRange defined for " + definition); return ErrorCodes.WTFErrorMedicalAttentionSuggested; } - return zone.IsUnitWithCategoryInRange(CategoryFlags.cf_pbs_docking_base, position, (int) typeExclusiveRange) - + return zone.IsUnitWithCategoryInRange(CategoryFlags.cf_pbs_docking_base, position, (int)typeExclusiveRange) ? ErrorCodes.PlacedTooCloseToPBSDockingbase : ErrorCodes.NoError; } - - public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, EntityDefault entityDefault) { List badSlopes; @@ -170,30 +212,32 @@ public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, E return ErrorCodes.ConsistencyError; } - var contructionRadius = (int) entityDefault.Config.constructionRadius; - var blockingRadius = (int) entityDefault.Config.blockingradius; - if (!zone.Configuration.Terraformable) { return ErrorCodes.ZoneNotTerraformable; } - if (!(entityDefault.CategoryFlags.IsCategory(CategoryFlags.cf_pbs_mining_towers) || - entityDefault.CategoryFlags.IsCategory(CategoryFlags.cf_pbs_control_tower) || - entityDefault.CategoryFlags.IsCategory(CategoryFlags.cf_pbs_energy_well) || - entityDefault.CategoryFlags.IsCategory(CategoryFlags.cf_pbs_highway_node))) - { + var contructionRadius = (int)entityDefault.Config.constructionRadius; + var blockingRadius = (int)entityDefault.Config.blockingradius; + if (entityDefault.Name == DefinitionNames.PBS_EXPIRING_DOCKING_BASE) + { + var terrainControlInfo = zone.Terrain.Controls.GetValue(position); + if (!terrainControlInfo.IsAnyTerraformProtected) + { + return ErrorCodes.OnlyBuildableOnTerraformProtected; + } + } + else if (!IsPlaceableOutsideOfBase(entityDefault.CategoryFlags)) + { var terrainControlInfo = zone.Terrain.Controls.GetValue(position); if (terrainControlInfo.IsAnyTerraformProtected) { return ErrorCodes.TileTerraformProtected; } - } - var centerPosition = position.Center; if (zone.IsAnyConstructibleWithinRadius2D(centerPosition, contructionRadius)) @@ -229,26 +273,57 @@ public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, E sessionError = ErrorCodes.TerrainTooSteepAndBlockedTilesWasFound; } - return sessionError; - } - private static void PushPlayersFromPosition(IZone zone, Position position, int range, int safeMargin = 1) + /// + /// Attempt to push a players from their current locations away from a newly constructed pbs + /// + /// Zone of pbs and players + /// Position of pbs deployment + /// The construction radius of the pbs + /// A safe margin to pull inside of the radius + private static void PushPlayersFromPosition(IZone zone, Position position, int range, double safeMargin = 0.25) { - + var epslion = 0.1; + var safeRadius = range - safeMargin; var playersInRange = zone.Players.WithinRange(position, range); foreach (var player in playersInRange) { - var pushPos = position.GetPositionTowards2D(player.CurrentPosition, Math.Max(range - safeMargin, 0.05)); + // Strategy 1: Push the player to the closest tangent to the building radius + var pushPos = position.GetPositionTowards2D(player.CurrentPosition, Math.Max(safeRadius, epslion)); + // Strategy 2: Check if the position satisfies the constraints, if fail try a new method + pushPos = TryMovePlayerOutOfRadius(pushPos, position, zone, player, safeRadius); Logger.DebugInfo($"player bumped from:{player.CurrentPosition.ToDoubleString2D()} to:{pushPos.ToDoubleString2D()} distance:{pushPos.TotalDistance2D(player.CurrentPosition)}"); - player.CurrentPosition = pushPos; - player.SendForceUpdate(); + player.TeleportToPositionAsync(pushPos, false, false); } } + /// + /// Fallback method for PushPlayersFromPosition. + /// Attempt to find an acceptable location on a radius around a center point to put a player. + /// + /// Attempted location + /// Center of circle to move player outside of + /// Zone of transform + /// Player to move + /// Radius of circle + /// Number of attempts to try + /// Epsilon factor to account for rounding in geometric in/equalities + /// Modified position + private static Position TryMovePlayerOutOfRadius(Position edge, Position center, IZone zone, Player player, double radius, int iterations=100, double epsilon=0.1) + { + var r = new Random(); + while ((edge.TotalDistance2D(center) < radius - epsilon || !zone.IsWalkable(edge, player.Slope)) && iterations > 0) + { + edge = center.OffsetInDirection(r.NextDouble(), radius); + iterations--; + } + return edge; + } + public static void OnPBSEggRemoved(IZone zone, PBSEgg pbsEgg) { DoTerraformProtectionForEgg(zone, pbsEgg, false); @@ -841,7 +916,7 @@ private static ErrorCodes HandleNodeDead(IZone zone, T pbsNode, Unit killer) //ha minden oke akkor majd dobjuk a lootot LootContainer.Create() .AddLoot(GetLootFromCapsule(pbsNode)) - .AddLoot(GetConstructionAmmoLootOnDead(pbsNode)) + .AddLoot(ConstructionAmmoHelpers.GetConstructionAmmoLootOnDead(pbsNode)) .BuildAndAddToZone(zone, lootPosition); Transaction.Current.OnCommited(() => @@ -853,38 +928,6 @@ private static ErrorCodes HandleNodeDead(IZone zone, T pbsNode, Unit killer) return ec; } - public const int CONSTRUCTION_AMMO_DEFINITION = 4658; - - public static IEnumerable GetConstructionAmmoLootOnDead(IPBSObject pbsObject) - { - var constructionLevelMax = pbsObject.ConstructionLevelMax; - - var amount = (int)(constructionLevelMax*0.7*pbsObject.ConstructionLevelCurrent/constructionLevelMax); - - var constructionLootList = new List(); - - if (amount > 0) - { - constructionLootList.Add(LootItemBuilder.Create(CONSTRUCTION_AMMO_DEFINITION).SetQuantity(amount).Build()); - } - - return constructionLootList; - } - - public static IEnumerable GetConstructionAmmoLootOnDeconstruct(IPBSObject pbsObject) - { - var amount = (int) (pbsObject.ConstructionLevelMax*0.7); - - var constructionLootList = new List(); - - if (amount > 0) - { - constructionLootList.Add(LootItemBuilder.Create(CONSTRUCTION_AMMO_DEFINITION).SetQuantity(amount).Build()); - } - - return constructionLootList; - } - public static void DropLootToZoneFromBase(IZone zone, PBSDockingBase pbsDockingBase, Unit killer) { Logger.DebugInfo("async hand dead started"); @@ -915,7 +958,7 @@ public static ErrorCodes HandleDockingBaseDead(IZone zone, PBSDockingBase pbsDoc LootContainer.Create() .AddLoot(GetLootFromCapsule(pbsDockingBase)) - .AddLoot(GetConstructionAmmoLootOnDead(pbsDockingBase)) + .AddLoot(ConstructionAmmoHelpers.GetConstructionAmmoLootOnDead(pbsDockingBase)) .BuildAndAddToZone(zone, lootPosition); //eddig jo innen 0sszeszedjuk sqlbol a cuccot ami van a bazison, hozzadjuk a loot hoz, es kesz @@ -1182,7 +1225,7 @@ private static void WaitForLootGenerator(PBSDockingBase dockingBase) } - public static ErrorCodes DeletePBSDockingBase(IZone zone, PBSDockingBase dockingBase) + public static ErrorCodes DeletePBSDockingBase(int zone, PBSDockingBase dockingBase) { ErrorCodes ec; @@ -1230,8 +1273,9 @@ public static void SendBaseDestroyed(Dictionary infoBaseDeadWhil } - public static Dictionary GetUpdateDictionary(IZone zone,Unit eventSource,PBSEventType pbsEventType, Dictionary data = null) + public static Dictionary GetUpdateDictionary(int zone,Unit eventSource,PBSEventType pbsEventType, Dictionary data = null) { + Logger.Warning($"Zone {zone} eventSource {eventSource} event type {pbsEventType}"); var sourceDict = eventSource.ToDictionary(); if (data == null) @@ -1241,7 +1285,7 @@ public static Dictionary GetUpdateDictionary(IZone zone,Unit eve #endif data.Add(k.message, (int)pbsEventType); data.Add(k.source, sourceDict); - data[k.zoneID] = zone.Id; //zoneid legyen mindig. + data[k.zoneID] = zone; //OPP: client does not accept null or invalid zoneIDs! return data; } @@ -1570,7 +1614,11 @@ public static void CheckWallPlantingAndThrow(IZone zone, Unit[] unitsInZone, Pos unitsInZone.WithinRange(targetPosition, DistanceConstants.PLANT_MAX_DISTANCE_FROM_PBS) .Any(u => { - if (u is PBSDockingBase || u is PBSControlTower) + if (u is ExpiringPBSDockingBase) + { + return false; + } + else if (u is PBSDockingBase || u is PBSControlTower) { if (u.Owner == corporationEid) return true; diff --git a/src/Perpetuum/Zones/PBS/PBSInterfaces.cs b/src/Perpetuum/Zones/PBS/PBSInterfaces.cs index b5bff3fa5..507dc6aeb 100644 --- a/src/Perpetuum/Zones/PBS/PBSInterfaces.cs +++ b/src/Perpetuum/Zones/PBS/PBSInterfaces.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Perpetuum.Accounting.Characters; +using Perpetuum.EntityFramework; using Perpetuum.Groups.Corporations; using Perpetuum.Log; using Perpetuum.Players; @@ -65,6 +66,9 @@ public interface IPBSObject void TakeOver(long newOwner); bool IsOrphaned { get; set; } + int ZoneIdCached { get; } + EntityDefault ED { get; } + event Action OrphanedStateChanged; void SendNodeUpdate(PBSEventType eventType = PBSEventType.nodeUpdate); diff --git a/src/Perpetuum/Zones/PBS/PBSObject.cs b/src/Perpetuum/Zones/PBS/PBSObject.cs index a73fafb98..fa24bb566 100644 --- a/src/Perpetuum/Zones/PBS/PBSObject.cs +++ b/src/Perpetuum/Zones/PBS/PBSObject.cs @@ -16,6 +16,7 @@ public abstract class PBSObject : Unit, IPBSObject { private PBSObjectHelper _pbsObjectHelper; private PBSReinforceHandler _reinforceHandler; + public int ZoneIdCached { get; private set; } IPBSReinforceHandler IPBSObject.ReinforceHandler => _reinforceHandler; @@ -87,6 +88,7 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { _pbsObjectHelper.Init(); _reinforceHandler.Init(); + ZoneIdCached = zone.Id; base.OnEnterZone(zone, enterType); } diff --git a/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs b/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs index 95d58403e..6701f6259 100644 --- a/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs @@ -32,6 +32,7 @@ public PBSObjectHelper(T pbsUnit) { _pbsUnit = pbsUnit; _pbsUnit.DamageTaken += OnUnitDamageTaken; + _pbsUnit.Dead += _pbsUnit_Dead; ConnectionHandler = new PBSConnectionHandler(pbsUnit); _constructionLevelCurrent = new UnitOptionalProperty(_pbsUnit,UnitDataType.ConstructionLevelCurrent,k.constructionLevelCurrent,() => 1); @@ -40,6 +41,12 @@ public PBSObjectHelper(T pbsUnit) _saver = new PBSObjectSaver(Entity.Repository, TimeSpan.FromMinutes(15)); } + private void _pbsUnit_Dead(Unit arg1, Unit arg2) + { + _pbsUnit.DamageTaken -= OnUnitDamageTaken; + _pbsUnit.Dead -= _pbsUnit_Dead; + } + private int _damageTaken; private void OnUnitDamageTaken(Unit unit, Unit attacker, DamageTakenEventArgs e) @@ -49,13 +56,19 @@ private void OnUnitDamageTaken(Unit unit, Unit attacker, DamageTakenEventArgs e) if (Interlocked.CompareExchange(ref _damageTaken, 1, 0) == 1) return; - Logger.Info($"PBS node attacked ({_pbsUnit.Eid}) attacker: {attacker.InfoString}"); + Logger.DebugInfo($"PBS node attacked ({_pbsUnit.Eid}) attacker: {attacker.InfoString}"); + + if (_pbsUnit.States.Dead) + return; Task.Delay(TimeSpan.FromSeconds(30)).ContinueWith(t => { try { - _pbsUnit.SendNodeUpdate(PBSEventType.nodeAttacked); + if (!_pbsUnit.States.Dead) + { + SendAttackAlert(); + } } finally { @@ -64,6 +77,11 @@ private void OnUnitDamageTaken(Unit unit, Unit attacker, DamageTakenEventArgs e) }); } + public void SendAttackAlert() + { + _pbsUnit.SendNodeUpdate(PBSEventType.nodeAttacked); + } + public void Init() { if (_pbsUnit.DynamicProperties.Contains(k.isOnline)) @@ -247,7 +265,7 @@ private void OnConstructionLevelChanged() { LootContainer.Create() .AddLoot(PBSHelper.GetCapsuleDefinitionByPBSObject(_pbsUnit),1) - .AddLoot(PBSHelper.GetConstructionAmmoLootOnDeconstruct(_pbsUnit)) + .AddLoot(ConstructionAmmoHelpers.GetConstructionAmmoLootOnDeconstruct(_pbsUnit)) .BuildAndAddToZone(zone, _pbsUnit.CurrentPosition); var dockingBase = _pbsUnit as PBSDockingBase; @@ -454,7 +472,7 @@ public Dictionary GetUpdateDictionary(PBSEventType pbsEventType,D #endif data.Add(k.message,(int)pbsEventType); data.Add(k.source,sourceDict); - data[k.zoneID] = _pbsUnit.Zone?.Id ?? -1; //zoneid legyen mindig. + data[k.zoneID] = _pbsUnit.ZoneIdCached; //OPP: use cached value to avoid accessing null zone on death return data; } } diff --git a/src/Perpetuum/Zones/PBS/PBSReinforceHandler.cs b/src/Perpetuum/Zones/PBS/PBSReinforceHandler.cs index d94662cae..767b42199 100644 --- a/src/Perpetuum/Zones/PBS/PBSReinforceHandler.cs +++ b/src/Perpetuum/Zones/PBS/PBSReinforceHandler.cs @@ -917,8 +917,8 @@ public void AddToDictionary(IDictionary info) public const double REINFORCE_THRESHOLD = 0.5; - public const int VULNERABLE_LENGTH_MINUTES = 5; //2 hours - public const int REINFORCE_LENGTH_MINUTES = 6; //3 days + public const int VULNERABLE_LENGTH_MINUTES = 15; //2 hours + public const int REINFORCE_LENGTH_MINUTES = 1; //3 days public const int REINFORCE_COUNTER_INCREASE_MINUTES = 6; //2 days diff --git a/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs b/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs index 60bad1011..72207dee7 100644 --- a/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs +++ b/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs @@ -38,6 +38,7 @@ public PBSTurret(IStandingHandler standingHandler,PBSObjectHelper.Fac _blobEmitter = new BlobEmitter(this); _reinforceHandler = new PBSReinforceHandler(this); _coreUseHandler = new CoreUseHandler(this, new EnergyStateFactory(this)); + } private double GetCoreMinimumFromModules() @@ -48,6 +49,8 @@ private double GetCoreMinimumFromModules() return maxCoreUsage; } + public int ZoneIdCached { get; private set; } + public IBlobHandler BlobHandler => _blobHandler; public IPBSReinforceHandler ReinforceHandler => _reinforceHandler; @@ -108,6 +111,8 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) AI.ToInactiveAI(); + ZoneIdCached = zone.Id; + base.OnEnterZone(zone, enterType); } @@ -191,7 +196,7 @@ public double BlobEmissionRadius get { return _blobEmitter.BlobEmissionRadius; } } - internal override bool IsHostile(Player player) + public override bool IsHostile(Player player) { return IsHostileCorporation(player.CorporationEid); } diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs new file mode 100644 index 000000000..fbf8db883 --- /dev/null +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs @@ -0,0 +1,342 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Groups.Corporations; +using Perpetuum.Log; +using Perpetuum.Players; +using Perpetuum.Timers; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using Perpetuum.Zones.PBS; +using Perpetuum.Zones.Teleporting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.ProximityProbes +{ + public interface ICharactersRegistered + { + Character[] GetRegisteredCharacters(); + void ReloadRegistration(); + int GetMaxRegisteredCount(); + } + + //ez van kinn a terepen + public abstract class ProximityDeviceBase : Unit, ICharactersRegistered + { + private readonly CharactersRegisterHelper _charactersRegisterHelper; + private IntervalTimer _probingInterval = new IntervalTimer(TimeSpan.FromSeconds(10)); + private UnitDespawnHelper _despawnHelper; + + protected ProximityDeviceBase() + { + _charactersRegisterHelper = new CharactersRegisterHelper(this); + } + + public ICorporationManager CorporationManager { get; set; } + + public virtual void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) + { + zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.PROXIMITY_PROBE_DEPLOY_RANGE_FROM_BASE).Any().ThrowIfTrue(ErrorCodes.NotDeployableNearObject); + zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.PROXIMITY_PROBE_DEPLOY_RANGE_FROM_TELEPORT).Any().ThrowIfTrue(ErrorCodes.TeleportIsInRange); + zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.PROXIMITY_PROBE_DEPLOY_RANGE_FROM_PROBE).Any().ThrowIfTrue(ErrorCodes.TooCloseToOtherDevice); + } + + public void SetDespawnTime(TimeSpan despawnTime) + { + _despawnHelper = UnitDespawnHelper.Create(this, despawnTime); + _despawnHelper.DespawnStrategy = Kill; + } + + // TODO: have to find better solution + public UnitDespawnHelper GetDespawnHelper() + { + return _despawnHelper; + } + + protected internal override void UpdatePlayerVisibility(Player player) + { + UpdateVisibility(player); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + + _ = _probingInterval.Update(time); + + if (!_probingInterval.Passed) + { + return; + } + + _probingInterval.Reset(); + + if (IsActive) + { + //detect + List robotsNearMe = GetNoticedUnits(); + + //do something + OnUnitsFound(robotsNearMe); + } + + if (_despawnHelper == null) + { + Items.ItemPropertyModifier m = GetPropertyModifier(AggregateField.despawn_time); + TimeSpan timespan = TimeSpan.FromMilliseconds((int)m.Value); + SetDespawnTime(timespan); + } + + _despawnHelper.Update(time, this); + } + + protected virtual bool IsActive => true; + + #region registration + + //ezt kell hivogatni requestbol, ha valtozott + public void ReloadRegistration() + { + _charactersRegisterHelper.ReloadRegistration(); + } + + public Character[] GetRegisteredCharacters() + { + return _charactersRegisterHelper.GetRegisteredCharacters(); + } + + public int GetMaxRegisteredCount() + { + return _charactersRegisterHelper.GetMaxRegisteredCount(); + } + + #endregion + + #region probe functions + + // egy adott pillanatban kiket lat + [CanBeNull] + public abstract List GetNoticedUnits(); + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + base.OnEnterZone(zone, enterType); + _probingInterval = new IntervalTimer(GetProximityCheckInterval(), true); + } + + public virtual void OnDeviceDead() + { + //uccso info a regisztraltaknak + SendDeviceDead(); + PBSRegisterHelper.ClearMembersFromSql(Eid); + Zone.UnitService.RemoveUserUnit(this); + Logger.Info("probe got deleted " + Eid); + } + + public virtual void OnDeviceCreated() + { + //elso info arrol hogy letrejott + + Logger.Info("probe created " + Eid); + + SendDeviceCreated(); + + } + + + public virtual void OnUnitsFound(List unitsFound) + { + //itt lehet mindenfele, pl most kuldunk egy kommandot amire a kliens terkepet frissit + + if (unitsFound.Count <= 0) + { + return; + } + + Character[] registerdCharacters = GetRegisteredCharacters(); + + if (registerdCharacters.Length <= 0) + { + return; + } + + Dictionary infoDict = CreateInfoDictionaryForProximityProbe(unitsFound); + + Message.Builder.SetCommand(Commands.ProximityProbeInfo).WithData(infoDict).ToCharacters(registerdCharacters).Send(); + } + + #endregion + + protected override void OnDead(Unit killer) + { + OnDeviceDead(); + base.OnDead(killer); + } + + public Dictionary GetProbeInfo(bool includeRegistered = true) + { + Dictionary info = BaseInfoToDictionary(); + + Dictionary probeDict = new Dictionary(); + + if (includeRegistered) + { + probeDict.Add(k.registered, GetRegisteredCharacters().GetCharacterIDs().ToArray()); + } + + probeDict.Add(k.zoneID, Zone.Id); + probeDict.Add(k.x, CurrentPosition.X); + probeDict.Add(k.y, CurrentPosition.Y); + info.Add("probe", probeDict); + return info; + } + + /// + /// All info included + /// + /// + public override Dictionary ToDictionary() + { + return GetProbeInfo(); + } + + public void SendDeviceCreated() + { + IEnumerable membersToInfom = GetAllPossibleMembersToInfom(); + Message.Builder.SetCommand(Commands.ProximityProbeCreated).WithData(ToDictionary()).ToCharacters(membersToInfom).Send(); + } + + public void SendUpdateToAllPossibleMembers() + { + IEnumerable members = GetAllPossibleMembersToInfom(); + + Message.Builder.SetCommand(Commands.ProximityProbeUpdate).WithData(ToDictionary()).ToCharacters(members).Send(); + } + + private void SendDeviceDead() + { + IEnumerable membersToInfom = GetAllPossibleMembersToInfom(); + + Message.Builder.SetCommand(Commands.ProximityProbeDead).WithData(ToDictionary()).ToCharacters(membersToInfom).Send(); + } + + public IEnumerable GetAllPossibleMembersToInfom() + { + return GetRegisteredCharacters().Concat(GetProximityBoard(Owner)).Distinct(); + } + + private IEnumerable GetProximityBoard(long corporationEid) + { + const CorporationRole roleMask = CorporationRole.CEO | CorporationRole.DeputyCEO | CorporationRole.Accountant; + return CorporationManager.LoadCorporationMembersWithAnyRole(corporationEid, roleMask); + } + + public Dictionary CreateInfoDictionaryForProximityProbe(List unitsFound) + { + Dictionary infoDict = GetProbeInfo(false); + + Dictionary unitsInfo = unitsFound.ToDictionary("c", p => + { + return new Dictionary + { + {k.characterID, p.Character.Id}, + {k.x, p.CurrentPosition.X}, + {k.y, p.CurrentPosition.Y} + }; + }); + + infoDict.Add(k.units, unitsInfo); + + return infoDict; + } + + public void Init(IEnumerable summonerCharacters) + { + PBSRegisterHelper.WriteRegistersToDb(Eid, summonerCharacters); + _probingInterval.Interval = TimeSpan.FromMilliseconds(GetProximityCheckInterval()); + } + + public virtual int GetProximityCheckInterval() + { + DefinitionConfig config = EntityDefault.Get(Definition).Config; + + if (config.cycle_time == null) + { + Logger.Error("consistency error in proximity device. interval not defined. " + Definition + " " + ED.Name); + return 150000; + } + + return ((int)config.cycle_time) + FastRandom.NextInt(0, 250); + } + + public bool IsRegistered(Character character) + { + return GetRegisteredCharacters().Contains(character); + } + + public ErrorCodes HasAccess(Character character) + { + if (IsRegistered(character)) + { + return ErrorCodes.NoError; + } + + long corporationEid = character.CorporationEid; + + if (corporationEid != Owner) + { + return ErrorCodes.AccessDenied; + } + + CorporationRole role = Corporation.GetRoleFromSql(character); + + return IsAllProbesVisible(role) ? ErrorCodes.NoError : ErrorCodes.AccessDenied; + } + + public Dictionary GetProbeRegistrationInfo() + { + Corporation ownerCorporation = Corporation.GetOrThrow(Owner); + int maxRegistered = ownerCorporation.GetMaximumRegisteredProbesAmount(); + int currentRegistered = GetRegisteredCharacters().Length; + int boardMembers = ownerCorporation.GetBoardMembersCount(); + + Dictionary result = new Dictionary + { + {k.eid, Eid }, + {"maxRegistered", maxRegistered}, + {"freeSlots", maxRegistered - (currentRegistered - boardMembers).Clamp(0, int.MaxValue)}, + {"currentlyRegistered", currentRegistered}, + {"boardMembers", boardMembers}, + }; + + return result; + } + + public static bool IsAllProbesVisible(CorporationRole role) + { + return role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO); + } + + public override void OnInsertToDb() + { + DynamicProperties.Update(k.currentCore, Core); + base.OnInsertToDb(); + } + + public override void OnUpdateToDb() + { + DynamicProperties.Update(k.currentCore, Core); + base.OnUpdateToDb(); + } + } +} diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs index 73555d674..0cfb05719 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs @@ -1,297 +1,57 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Perpetuum.Accounting.Characters; using Perpetuum.EntityFramework; -using Perpetuum.Groups.Corporations; -using Perpetuum.Log; +using Perpetuum.ExportedTypes; using Perpetuum.Players; -using Perpetuum.Timers; -using Perpetuum.Units; -using Perpetuum.Zones.PBS; +using Perpetuum.Zones.Blobs.BlobEmitters; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Zones.ProximityProbes { - public interface ICharactersRegistered + public class ProximityProbe : ProximityDeviceBase, IBlobEmitter { - Character[] GetRegisteredCharacters(); - void ReloadRegistration(); - int GetMaxRegisteredCount(); - } - - //ez van kinn a terepen - public abstract class ProximityProbeBase : Unit , ICharactersRegistered - { - private readonly CharactersRegisterHelper _charactersRegisterHelper; - private IntervalTimer _probingInterval = new IntervalTimer(TimeSpan.FromSeconds(10)); - - protected ProximityProbeBase() - { - _charactersRegisterHelper = new CharactersRegisterHelper(this); - } - - public ICorporationManager CorporationManager { get; set; } - protected internal override void UpdatePlayerVisibility(Player player) { UpdateVisibility(player); } - public override void AcceptVisitor(IEntityVisitor visitor) + public override List GetNoticedUnits() { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); + return GetVisibleUnits().Select(v => v.Target).OfType().ToList(); } - protected override void OnUpdate(TimeSpan time) + protected override bool IsActive { - base.OnUpdate(time); - - _probingInterval.Update(time); - - if (!_probingInterval.Passed) - return; - - _probingInterval.Reset(); - - if (IsActive) + get { - //detect - var robotsNearMe = GetNoticedUnits(); - - //do something - OnUnitsFound(robotsNearMe); + double coreRatio = Core.Ratio(CoreMax); + return coreRatio > 0.98; } } - protected virtual bool IsActive => true; - - #region registration - - //ezt kell hivogatni requestbol, ha valtozott - public void ReloadRegistration() - { - _charactersRegisterHelper.ReloadRegistration(); - } - - public Character[] GetRegisteredCharacters() - { - return _charactersRegisterHelper.GetRegisteredCharacters(); - } - - public int GetMaxRegisteredCount() - { - return _charactersRegisterHelper.GetMaxRegisteredCount(); - } - - #endregion - - #region probe functions - - // egy adott pillanatban kiket lat - [CanBeNull] - public abstract List GetNoticedUnits(); - - protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) - { - base.OnEnterZone(zone, enterType); - _probingInterval = new IntervalTimer(GetProbeInterval(), true); - } - - public virtual void OnProbeDead() - { - //uccso info a regisztraltaknak - SendProbeDead(); - - PBSRegisterHelper.ClearMembersFromSql(Eid); - - Zone.UnitService.RemoveUserUnit(this); - - Logger.Info("probe got deleted " + Eid); - } - - public virtual void OnProbeCreated() - { - //elso info arrol hogy letrejott - - Logger.Info("probe created " + Eid); - - SendProbeCreated(); - - } - - - public virtual void OnUnitsFound(List unitsFound) + public double BlobEmission { - //itt lehet mindenfele, pl most kuldunk egy kommandot amire a kliens terkepet frissit - - if (unitsFound.Count <= 0) return; - - var registerdCharacters = GetRegisteredCharacters(); - - if (registerdCharacters.Length <= 0) return; - - var infoDict = CreateInfoDictionaryForProximityProbe(unitsFound); - - Message.Builder.SetCommand(Commands.ProximityProbeInfo).WithData(infoDict).ToCharacters(registerdCharacters).Send(); - } - - #endregion - - protected override void OnDead(Unit killer) - { - OnProbeDead(); - base.OnDead(killer); - } - - public Dictionary GetProbeInfo(bool includeRegistered = true) - { - var info = BaseInfoToDictionary(); - - var probeDict = new Dictionary(); - - if (includeRegistered) + get { - probeDict.Add(k.registered, GetRegisteredCharacters().GetCharacterIDs().ToArray()); + Items.ItemPropertyModifier blobEmission = GetPropertyModifier(AggregateField.blob_emission); + return blobEmission.Value; } - - probeDict.Add(k.zoneID, Zone.Id); - probeDict.Add(k.x, CurrentPosition.X); - probeDict.Add(k.y, CurrentPosition.Y); - info.Add("probe", probeDict); - return info; - } - - /// - /// All info included - /// - /// - public override Dictionary ToDictionary() - { - return GetProbeInfo(); - } - - public void SendProbeCreated() - { - var membersToInfom = GetAllPossibleMembersToInfom(); - Message.Builder.SetCommand(Commands.ProximityProbeCreated).WithData(ToDictionary()).ToCharacters(membersToInfom).Send(); - } - - public void SendUpdateToAllPossibleMembers() - { - var members = GetAllPossibleMembersToInfom(); - - Message.Builder.SetCommand(Commands.ProximityProbeUpdate).WithData(ToDictionary()).ToCharacters(members).Send(); - } - - private void SendProbeDead() - { - var membersToInfom = GetAllPossibleMembersToInfom(); - - Message.Builder.SetCommand(Commands.ProximityProbeDead).WithData(ToDictionary()).ToCharacters(membersToInfom).Send(); - } - - public IEnumerable GetAllPossibleMembersToInfom() - { - return GetRegisteredCharacters().Concat(GetProximityBoard(Owner)).Distinct(); } - private IEnumerable GetProximityBoard(long corporationEid) + public double BlobEmissionRadius { - const CorporationRole roleMask = CorporationRole.CEO | CorporationRole.DeputyCEO | CorporationRole.Accountant; - return CorporationManager.LoadCorporationMembersWithAnyRole(corporationEid,roleMask); - } - - public Dictionary CreateInfoDictionaryForProximityProbe( List unitsFound) - { - var infoDict = GetProbeInfo(false); - - var unitsInfo = unitsFound.ToDictionary("c", p => + get { - return new Dictionary - { - {k.characterID, p.Character.Id}, - {k.x, p.CurrentPosition.X}, - {k.y, p.CurrentPosition.Y} - }; - }); - - infoDict.Add(k.units, unitsInfo); - - return infoDict; - } - - public void InitProbe(IEnumerable summonerCharacters ) - { - PBSRegisterHelper.WriteRegistersToDb(Eid, summonerCharacters); - _probingInterval.Interval = TimeSpan.FromMilliseconds(GetProbeInterval()); - } - - public virtual int GetProbeInterval() - { - var config = EntityDefault.Get(Definition).Config; - - if (config.cycle_time == null) - { - Logger.Error("consistency error in proximityProbe. interval not defined. " + Definition + " " + ED.Name); - return 150000; + Items.ItemPropertyModifier blobEmissionRadius = GetPropertyModifier(AggregateField.blob_emission_radius); + return blobEmissionRadius.Value; } - - return ((int) config.cycle_time) + FastRandom.NextInt(0,250); } - public bool IsRegistered(Character character) - { - return GetRegisteredCharacters().Contains(character); - } - - public ErrorCodes HasAccess(Character character) + public override void AcceptVisitor(IEntityVisitor visitor) { - if (IsRegistered(character)) - { - return ErrorCodes.NoError; - } - - var corporationEid = character.CorporationEid; - - if (corporationEid != Owner) - { - return ErrorCodes.AccessDenied; - } - - var role = Corporation.GetRoleFromSql(character); - - if (IsAllProbesVisible(role)) + if (!TryAcceptVisitor(this, visitor)) { - return ErrorCodes.NoError; + base.AcceptVisitor(visitor); } - - return ErrorCodes.AccessDenied; - } - - public Dictionary GetProbeRegistrationInfo() - { - var ownerCorporation = Corporation.GetOrThrow(Owner); - var maxRegistered = ownerCorporation.GetMaximumRegisteredProbesAmount(); - var currentRegistered = GetRegisteredCharacters().Length; - var boardMembers = ownerCorporation.GetBoardMembersCount(); - - var result = new Dictionary - { - {k.eid, Eid }, - {"maxRegistered", maxRegistered}, - {"freeSlots", (maxRegistered - (currentRegistered - boardMembers).Clamp(0, int.MaxValue))}, - {"currentlyRegistered", currentRegistered}, - {"boardMembers", boardMembers}, - }; - - return result; - } - - public static bool IsAllProbesVisible(CorporationRole role) - { - return role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO); } } - -} +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs index 4fda97dcd..7a55ece82 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs @@ -1,19 +1,21 @@ -using System.Linq; +using System; +using System.Linq; using Perpetuum.Deployers; using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; using Perpetuum.Groups.Corporations; using Perpetuum.Players; using Perpetuum.Units; namespace Perpetuum.Zones.ProximityProbes { - public class VisibilityBasedProbeDeployer : ItemDeployer + public class ProximityProbeDeployer : ItemDeployer { - private readonly IEntityServices _entityServices; + private readonly IEntityServices entityServices; - public VisibilityBasedProbeDeployer(IEntityServices entityServices) : base(entityServices) + public ProximityProbeDeployer(IEntityServices entityServices) : base(entityServices) { - _entityServices = entityServices; + this.entityServices = entityServices; } protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, Player player) @@ -25,7 +27,8 @@ protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, var maxProbes = corporation.GetMaximumProbeAmount(); corporation.GetProximityProbeEids().Count().ThrowIfGreaterOrEqual(maxProbes, ErrorCodes.MaximumAmountOfProbesReached); - var probe = (ProximityProbeBase)_entityServices.Factory.CreateWithRandomEID(DeployableItemEntityDefault); + var probe = (ProximityDeviceBase)entityServices.Factory.CreateWithRandomEID(DeployableItemEntityDefault); + probe.CheckDeploymentAndThrow(zone, spawnPosition); //Enforce min-distance separations probe.Owner = corporation.Eid; var zoneStorage = zone.Configuration.GetStorage(); probe.Parent = zoneStorage.Eid; @@ -36,8 +39,18 @@ protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, var initialMembers = corporation.GetMembersWithAnyRoles(CorporationRole.CEO, CorporationRole.DeputyCEO).Select(cm => cm.character).ToList(); initialMembers.Add(player.Character); - probe.InitProbe( initialMembers.Distinct() ); + probe.Init( initialMembers.Distinct() ); + probe.SetDespawnTime(this.ProximityProbeDespawnTime); return probe; } + + private TimeSpan ProximityProbeDespawnTime + { + get + { + var m = GetPropertyModifier(AggregateField.despawn_time); + return TimeSpan.FromMilliseconds((int)m.Value); + } + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/ProximityProbes/VisibilityBasedProximityProbe.cs b/src/Perpetuum/Zones/ProximityProbes/VisibilityBasedProximityProbe.cs deleted file mode 100644 index ad42b5a7e..000000000 --- a/src/Perpetuum/Zones/ProximityProbes/VisibilityBasedProximityProbe.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Perpetuum.ExportedTypes; -using Perpetuum.Players; -using Perpetuum.Zones.Blobs.BlobEmitters; - -namespace Perpetuum.Zones.ProximityProbes -{ - public class VisibilityBasedProximityProbe : ProximityProbeBase , IBlobEmitter - { - protected internal override void UpdatePlayerVisibility(Player player) - { - UpdateVisibility(player); - } - - public override List GetNoticedUnits() - { - return GetVisibleUnits().Select(v=>v.Target).OfType().ToList(); - } - - protected override bool IsActive - { - get - { - var coreRatio = Core.Ratio(CoreMax); - return coreRatio > 0.98; - } - } - - public double BlobEmission - { - get - { - var blobEmission = GetPropertyModifier(AggregateField.blob_emission); - return blobEmission.Value; - } - } - - public double BlobEmissionRadius - { - get - { - var blobEmissionRadius = GetPropertyModifier(AggregateField.blob_emission_radius); - return blobEmissionRadius.Value; - } - } - - public override void OnInsertToDb() - { - DynamicProperties.Update(k.currentCore, Core); - base.OnInsertToDb(); - } - - public override void OnUpdateToDb() - { - DynamicProperties.Update(k.currentCore, Core); - base.OnUpdateToDb(); - } - } -} \ No newline at end of file diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs new file mode 100644 index 000000000..7a05b8711 --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -0,0 +1,161 @@ +using Perpetuum.Items; +using Perpetuum.Modules; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.Flocks; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Perpetuum.Zones.RemoteControl +{ + public class BandwidthHandler : ISmartCreatureGroup + { + private readonly RemoteControllerModule owner; + private List channels = new List(); + private readonly ConcurrentQueue newChannels = new ConcurrentQueue(); + private readonly ConcurrentQueue deactivatedChannels = new ConcurrentQueue(); + private int dirty; + + public string Name => owner.ParentRobot.GetCharacter().Nick; + + public IEnumerable Members => channels.Select(x => x.Turret); + + public BandwidthHandler(RemoteControllerModule owner) + { + this.owner = owner; + } + + public double BandwidthUsed + { + get + { + return channels.Any() + ? channels.Sum(x => x.Turret?.RemoteChannelBandwidthUsage ?? 0) + : 0; + } + } + + public List Turrets + { + get { return channels; } + } + + public bool HasFreeBandwidthFor(RemoteControlledUnit unit) + { + return BandwidthUsed <= owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; + } + + public void UseRemoteChannel(RemoteControlledCreature turret) + { + UseRemoteChannel(new RemoteChannel(owner, turret)); + } + + public void UseRemoteChannel(RemoteChannel newChannel) + { + newChannels.Enqueue(newChannel); + Interlocked.Exchange(ref dirty, 1); + } + + public void OnRemoteChannelDeactivated(RemoteControlledCreature turret) + { + var channel = GetRemoteChannelByUnit(turret); + + if (channel != null) + { + ReleaseRemoteChannel(channel); + turret.RemoteChannelDeactivated -= OnRemoteChannelDeactivated; + } + + Update(); + } + + public void CloseAllChannels() + { + foreach (var channel in channels) + { + channel.Turret.Kill(); + ReleaseRemoteChannel(channel); + } + + Update(); + } + + private void ReleaseRemoteChannel(RemoteChannel channel) + { + deactivatedChannels.Enqueue(channel); + Interlocked.Exchange(ref dirty, 1); + } + + private void ProcessReleasedChannels(IList channels) + { + RemoteChannel releasedChannel; + + while (deactivatedChannels.TryDequeue(out releasedChannel)) + { + channels.Remove(releasedChannel); + } + } + + public void Update() + { + if (Interlocked.CompareExchange(ref dirty, 0, 1) == 1) + { + var channelsToProcess = this.channels.ToList(); + + try + { + ProcessReleasedChannels(channelsToProcess); + ProcessNewChannels(channelsToProcess); + } + finally + { + this.channels = channelsToProcess; + } + } + } + + private void ProcessNewChannels(IList channels) + { + RemoteChannel newChannel; + while (newChannels.TryDequeue(out newChannel)) + { + if (channels.Any(l => l.Equals(newChannel))) + { + continue; + } + + channels.Add(newChannel); + } + } + + [CanBeNull] + public RemoteChannel GetRemoteChannel(long channelId) + { + if (channelId == 0) + { + return null; + } + + return channels.Find(l => l.Id == channelId); + } + + [CanBeNull] + public RemoteChannel GetRemoteChannelByUnit(Unit unit) + { + return GetRemoteChannelByEid(unit.Eid); + } + + public void AddDebugInfoToDictionary(IDictionary dictionary) + { + // Do nothing + } + + [CanBeNull] + private RemoteChannel GetRemoteChannelByEid(long unitEid) + { + return channels.OfType().FirstOrDefault(l => l.Turret.Eid == unitEid); + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs new file mode 100644 index 000000000..af07e3f17 --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs @@ -0,0 +1,133 @@ +using Perpetuum.EntityFramework; +using Perpetuum.Players; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Services.Standing; +using Perpetuum.Units; +using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.Gates; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.Teleporting; +using System; + +namespace Perpetuum.Zones.RemoteControl +{ + public class CombatDrone : RemoteControlledCreature + { + public double GuardRange { get; set; } + + public override bool IsStationary => false; + + public CombatDrone(IStandingHandler standingHandler) + : base(standingHandler) + { + } + + public bool IsInGuardRange => CurrentPosition.IsInRangeOf2D(HomePosition, GuardRange); + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + public override bool IsHostile(Player player) + { + return IsHostilePlayer(player); + } + + public override void OnAggression(Unit victim) + { + CommandRobot.OnAggression(victim); + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + protected override void OnUpdate(TimeSpan time) + { + HomePosition = CommandRobot.CurrentPosition; + base.OnUpdate(time); + } + + protected override bool IsDetected(Unit target) + { + return IsCommandBotPrimaryLock(target) && base.IsDetected(target); + } + + internal override bool IsHostile(Npc npc) + { + return true; + } + + internal override bool IsHostile(CombatDrone drone) + { + return !(drone.CommandRobot is Player player) || IsHostilePlayer(player); + } + + internal override bool IsHostile(SentryTurret turret) + { + return !(turret.CommandRobot is Player player) || IsHostilePlayer(player); + } + + internal override bool IsHostile(Rift rift) + { + return true; + } + + internal override bool IsHostile(Gate gate) + { + return true; + } + + internal override bool IsHostile(AreaBomb bomb) + { + return true; + } + + internal override bool IsHostile(IndustrialTurret turret) + { + return !(turret.CommandRobot is Player player) || IsHostilePlayer(player); + } + + internal override bool IsHostile(Portal portal) + { + return true; + } + + internal override bool IsHostile(MobileTeleport teleport) + { + return true; + } + + protected override void OnUnitLockStateChanged(Lock @lock) + { + // Do nothing; Combat Drones don't care about locks + } + + protected override void UpdateUnitVisibility(Unit target) + { + if (target is Npc || + target is RemoteControlledCreature || + target is Portal || + target is AreaBomb) + { + UpdateVisibility(target); + } + } + + private bool IsCommandBotPrimaryLock(Unit unit) + { + Lock primaryLock = CommandRobot.GetPrimaryLock(); + + return primaryLock != null && + primaryLock.State == Locking.LockState.Locked && + primaryLock is UnitLock && + (primaryLock as UnitLock).Target == unit; + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs new file mode 100644 index 000000000..04680ac6a --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -0,0 +1,109 @@ +using Perpetuum.Data; +using Perpetuum.Log; +using Perpetuum.Players; +using Perpetuum.Services.Looting; +using Perpetuum.Services.Standing; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Transactions; + +namespace Perpetuum.Zones.RemoteControl +{ + public class IndustrialTurret : RemoteControlledCreature + { + public TurretType TurretType { get; private set; } + + public IndustrialTurret(IStandingHandler standingHandler) + : base(standingHandler) + { + } + + public void SetTurretType(TurretType turretType) + { + TurretType = turretType; + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + public override bool IsHostile(Player player) + { + return false; + } + + internal override bool IsHostile(Npc npc) + { + return false; + } + + protected override void OnBeforeRemovedFromZone(IZone zone) + { + EjectCargo(zone); + base.OnBeforeRemovedFromZone(zone); + } + + public void EjectCargo(IZone zone) + { + using (TransactionScope scope = Db.CreateTransaction()) + { + try + { + Robots.RobotInventory robotInventory = GetContainer(); + + Debug.Assert(robotInventory != null); + + List lootItems = new List(); + + foreach (Items.Item item in robotInventory + .GetItems() + .Where(i => !i.ED.AttributeFlags.NonStackable)) + { + if (item.Quantity > 0) + { + lootItems.Add( + LootItemBuilder + .Create(item.Definition) + .SetQuantity(item.Quantity) + .SetRepackaged(item.ED.AttributeFlags.Repackable) + .Build()); + } + + robotInventory.RemoveItemOrThrow(item); + Repository.Delete(item); + } + + if (lootItems.Count > 0) + { + _ = LootContainer.Create() + .AddLoot(lootItems) + .BuildAndAddToZone(zone, CurrentPosition); + } + + Save(); + + scope.Complete(); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + } + } + + protected override void OnUpdate(TimeSpan time) + { + if (!IsInOperationalRange) + { + Kill(); + } + + base.OnUpdate(time); + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs new file mode 100644 index 000000000..188f31da6 --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs @@ -0,0 +1,39 @@ +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Modules; + +namespace Perpetuum.Zones.RemoteControl +{ + public delegate void RemoteChannelEventHandler(RemoteControlledCreature turret); + public delegate void RemoteChannelEventHandler(RemoteChannel channel, T arg); + + public class RemoteChannel + { + public long Id { get; private set; } + + public RemoteControllerModule Owner { get; private set; } + + public RemoteControlledCreature Turret { get; set; } + + public RemoteChannel(RemoteControllerModule owner, RemoteControlledCreature turret) + { + Id = FastRandom.NextLong(); + Owner = owner; + Turret = turret; + } + + public virtual bool Equals(Lock other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Id == other.Id; + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs new file mode 100644 index 000000000..c7f7a980a --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -0,0 +1,110 @@ +using Perpetuum.Players; +using Perpetuum.Robots; +using Perpetuum.Services.Standing; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; +using System; + +namespace Perpetuum.Zones.RemoteControl +{ + public class RemoteControlledCreature : SmartCreature + { + private const double SentryTurretCallForHelpArmorThreshold = 0.8; + private UnitDespawnHelper despawnHelper; + private readonly IStandingHandler standingHandler; + private const double StandingLimit = 0.0; + + public event RemoteChannelEventHandler RemoteChannelDeactivated; + + public Robot CommandRobot { get; private set; } + + public double RemoteChannelBandwidthUsage { get; private set; } + + public TimeSpan DespawnTime + { + set => despawnHelper = UnitDespawnHelper.Create(this, value); + } + + public bool IsInOperationalRange => CurrentPosition.IsInRangeOf2D(CommandRobot.CurrentPosition, HomeRange); + + public override bool IsStationary => true; + + public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; + + public RemoteControlledCreature(IStandingHandler standingHandler) + { + this.standingHandler = standingHandler; + } + + public void SetCommandRobot(Robot commandRobot) + { + CommandRobot = commandRobot; + } + + public void SetBandwidthUsage(double value) + { + RemoteChannelBandwidthUsage = value; + } + + public override void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) + { + if (hostile.IsPlayer() && CommandRobot == (hostile as Player)) + { + return; + } + + base.AddThreat(hostile, threat, spreadToGroup); + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + despawnHelper?.Update(time, this); + } + + protected override void OnBeforeRemovedFromZone(IZone zone) + { + RemoteChannelDeactivated(this); + } + + protected bool IsHostilePlayer(Player targetPlayer) + { + if (!(CommandRobot is Player player)) + { + return true; + } + + if (player == targetPlayer) + { + return false; + } + + if (Zone.Configuration.IsAlpha && !player.HasPvpEffect && !targetPlayer.HasPvpEffect) + { + return false; + } + + if (player == targetPlayer) + { + return false; + } + + if (player.Gang != null && player.Gang.IsMember(targetPlayer.Character)) + { + return false; + } + + double corporationStanding = standingHandler.GetStanding(player.CorporationEid, targetPlayer.CorporationEid); + + if (corporationStanding > StandingLimit) + { + return false; + } + + double personalStanding = standingHandler.GetStanding(Owner, targetPlayer.Character.Eid); + + return personalStanding <= StandingLimit; + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs new file mode 100644 index 000000000..0319fc4ad --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs @@ -0,0 +1,24 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Items.Ammos; + +namespace Perpetuum.Zones.RemoteControl +{ + public class RemoteControlledUnit : Ammo + { + private ItemProperty remoteChannelBandwidthUsage = ItemProperty.None; + + public override void Initialize() + { + remoteChannelBandwidthUsage = new AmmoProperty(this, AggregateField.remote_control_bandwidth_usage); + AddProperty(remoteChannelBandwidthUsage); + + base.Initialize(); + } + + public double RemoteChannelBandwidthUsage + { + get { return remoteChannelBandwidthUsage.Value; } + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs new file mode 100644 index 000000000..a183503dd --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -0,0 +1,87 @@ +using Perpetuum.EntityFramework; +using Perpetuum.Players; +using Perpetuum.Services.Standing; +using Perpetuum.Units; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem; +using System; + +namespace Perpetuum.Zones.RemoteControl +{ + public class SentryTurret : RemoteControlledCreature + { + public SentryTurret(IStandingHandler standingHandler) + : base(standingHandler) + { + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + public override bool IsHostile(Player targetPlayer) + { + return IsHostilePlayer(targetPlayer); + } + + public override void OnAggression(Unit victim) + { + CommandRobot.OnAggression(victim); + } + + internal override bool IsHostile(Npc npc) + { + return true; + } + + internal override bool IsHostile(CombatDrone drone) + { + return !(drone.CommandRobot is Player player) || IsHostilePlayer(player); + } + + internal override bool IsHostile(SentryTurret turret) + { + return !(turret.CommandRobot is Player player) || IsHostilePlayer(player); + } + + protected override void UpdateUnitVisibility(Unit target) + { + if (target is Npc || + target is RemoteControlledCreature) + { + UpdateVisibility(target); + } + } + + protected override void OnUpdate(TimeSpan time) + { + if (!IsInOperationalRange) + { + Kill(); + } + + base.OnUpdate(time); + } + + protected override void OnUnitLockStateChanged(Lock @lock) + { + // Do nothing; Sentry Turrets don't care about locks + } + + public override bool IsFriendly(Unit source) + { + Player player = Zone.ToPlayerOrGetOwnerPlayer(source); + + return player != null && IsHostilePlayer(player); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/RemoteControl/TurretType.cs b/src/Perpetuum/Zones/RemoteControl/TurretType.cs new file mode 100644 index 000000000..0e7cfd0a8 --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/TurretType.cs @@ -0,0 +1,10 @@ +namespace Perpetuum.Zones.RemoteControl +{ + public enum TurretType + { + Sentry, + Mining, + Harvesting, + CombatDrone, + } +} diff --git a/src/Perpetuum/Zones/Scanning/Modules/GeoScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/GeoScannerModule.cs index 97bb4a4a4..f058d9522 100644 --- a/src/Perpetuum/Zones/Scanning/Modules/GeoScannerModule.cs +++ b/src/Perpetuum/Zones/Scanning/Modules/GeoScannerModule.cs @@ -1,6 +1,7 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; using Perpetuum.Modules; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Zones.Scanning.Results; using Perpetuum.Zones.Scanning.Scanners; diff --git a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs index 90dabd36a..d21254f7a 100644 --- a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs +++ b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using Perpetuum.Items; using Perpetuum.Modules; using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; namespace Perpetuum.Zones.Scanning.Modules { @@ -13,33 +14,58 @@ public abstract class ItemScannerModule : ActiveModule { protected ItemScannerModule() : base(true) { - + + } + + protected override void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) + { + Player targetPlayer = unitLockTarget.Target is Player player + ? player + : unitLockTarget.Target is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer + : null; + + if (targetPlayer == null) + { + return; + } + + if (targetPlayer.HasPvpEffect || !targetPlayer.Zone.Configuration.Protected) + { + return; + } + + _ = (parentPlayer?.CheckPvp().ThrowIfError()); } protected override void OnAction() { - var unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); - var probability = GetProbability(unitLock.Target); + UnitLock unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + double probability = GetProbability(unitLock.Target); - var scannedItems = ScanItems(unitLock.Target).Where(i => FastRandom.NextDouble() <= probability).ToArray(); + ItemInfo[] scannedItems = ScanItems(unitLock.Target).Where(i => FastRandom.NextDouble() <= probability).ToArray(); - var packet = BuildScanResultPacket(unitLock.Target, scannedItems, probability); - var player = (Player) ParentRobot; + Packet packet = BuildScanResultPacket(unitLock.Target, scannedItems, probability); + Player player = (Player)ParentRobot; Debug.Assert(player != null, "player != null"); player.Session.SendPacket(packet); - OnTargetScanned(player,unitLock.Target); + OnTargetScanned(player, unitLock.Target); } + protected abstract IEnumerable ScanItems(Unit target); + + protected abstract Packet BuildScanResultPacket(Unit target, ItemInfo[] scannedItems, double probability); + + protected abstract void OnTargetScanned(Player player, Unit target); + private double GetProbability(Unit target) { - var probability = 1.0; - probability = ModifyValueByOptimalRange(target,probability); + double probability = 1.0; + probability = ModifyValueByOptimalRange(target, probability); + return probability; } - - protected abstract IEnumerable ScanItems(Unit target); - protected abstract Packet BuildScanResultPacket(Unit target, ItemInfo[] scannedItems, double probability); - protected abstract void OnTargetScanned(Player player, Unit target); } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Scanning/Scanners/Scanner.Tile.cs b/src/Perpetuum/Zones/Scanning/Scanners/Scanner.Tile.cs index fa4d6b8aa..25a197a1e 100644 --- a/src/Perpetuum/Zones/Scanning/Scanners/Scanner.Tile.cs +++ b/src/Perpetuum/Zones/Scanning/Scanners/Scanner.Tile.cs @@ -10,8 +10,8 @@ public partial class Scanner : IEntityVisitor public void Visit(TileScannerAmmo ammo) { var area = _zone.CreateArea(_player.CurrentPosition, ammo.ScanRange); - var builder = MineralScanResultBuilder.Create(_player.Zone, ammo.MaterialType); + builder.ScanArea = area; builder.ScanAccuracy = _module.ScanAccuracy; @@ -20,6 +20,7 @@ public void Visit(TileScannerAmmo ammo) _module.LastScanResult = result; var mineralLayer = _zone.Terrain.GetMaterialLayer(ammo.MaterialType) as MineralLayer; + if (mineralLayer != null) { if (result.FoundAny) diff --git a/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs new file mode 100644 index 000000000..e6dad788a --- /dev/null +++ b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs @@ -0,0 +1,125 @@ +using Perpetuum.Comparers; +using Perpetuum.EntityFramework; +using Perpetuum.Log; +using Perpetuum.Players; +using Perpetuum.Timers; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Perpetuum.Zones +{ + /// + /// A class used by a Zone to determine if a Player is orphaned by a timeout check on its ZoneSession + /// + public class SessionlessPlayerTimeout + { + private static readonly TimeSpan MAX_ORPHAN_TIME = TimeSpan.FromMinutes(3); + private static readonly TimeSpan UPDATE_RATE = TimeSpan.FromMinutes(1.5); + private readonly IZone _zone; + private readonly List _orphanPlayers = new List(); + + public SessionlessPlayerTimeout(IZone zone) + { + _zone = zone; + } + + private TimeSpan _elapsed = TimeSpan.Zero; + public void Update(TimeSpan time) + { + _elapsed += time; + if (_elapsed < UPDATE_RATE) + return; + + DoUpdate(); + _elapsed = TimeSpan.Zero; + } + + private void DoUpdate() + { + PopulateNewSessionlessPlayers(_zone, _orphanPlayers); + CleanValidSessions(_orphanPlayers); + RemoveExpiredOrphansFromZone(_orphanPlayers); + } + + private void PopulateNewSessionlessPlayers(IZone zone, List orphans) + { + var newOrphans = + zone.Players + .Where(p => p.Session == ZoneSession.None) + .Cast() + .Except(_orphanPlayers, new EntityComparer()) + .Cast() + .Select(p => new PlayerTimeout(p)); + + orphans.AddRange(newOrphans); + } + + + private void CleanValidSessions(List orphans) + { + orphans.RemoveAll(p => !p.Sessionless); + } + + private void RemoveExpiredOrphansFromZone(List orphans) + { + var toRemove = orphans.Where(o => o.Expired); + + // The Enumerable ForEach call was deprecated for good reason + // use a standard foreach loop so that you have compiler help + foreach (var orphan in toRemove) + orphan.RemoveFromZone(); + + orphans.RemoveAll(p => p.Expired); + } + + private class PlayerTimeout : IEntity + { + private readonly TimeKeeper _time; + private Player Player { get; } + + public bool Sessionless { get { return Player.Session == ZoneSession.None; } } + public bool Expired { get { return _time.Expired; } } + public long Eid { get { return Player is null ? 0 : Player.Eid; } } + + public PlayerTimeout(Player p) + { + _time = new TimeKeeper(MAX_ORPHAN_TIME); + Player = p; + } + + public void RemoveFromZone() + { + Logger.Warning($"Orphan Player removed from zone by timeout! {Player}"); + Player.RemoveFromZone(); + } + + public bool Equals(PlayerTimeout other) + { + if (other is null) + return false; + + return ReferenceEquals(this, other) || Eid == other.Eid; + } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + else if (obj is PlayerTimeout pt) + { + return Equals(pt); + } + return false; + } + + public override int GetHashCode() + { + return Eid.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/StrongHoldZone.cs b/src/Perpetuum/Zones/StrongHoldZone.cs new file mode 100644 index 000000000..4923eb8d6 --- /dev/null +++ b/src/Perpetuum/Zones/StrongHoldZone.cs @@ -0,0 +1,17 @@ +using Perpetuum.Groups.Gangs; +using Perpetuum.Services.Sessions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Zones +{ + public class StrongHoldZone : Zone + { + public StrongHoldZone(ISessionManager sessionManager, IGangManager gangManager) : base(sessionManager, gangManager) + { + } + } +} diff --git a/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs new file mode 100644 index 000000000..235337440 --- /dev/null +++ b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Players; + +namespace Perpetuum.Zones.Teleporting +{ + public class MobileStrongholdTeleport : MobileTeleport + { + private readonly StrongholdTeleportTargetHelper _strongholdTeleportTargetHelper; + + private TeleportDescription _usedTeleportDescription; + + public MobileStrongholdTeleport(StrongholdTeleportTargetHelper targetHelper, TeleportDescriptionBuilder.Factory descriptionBuilderFactory) : base(descriptionBuilderFactory) + { + _strongholdTeleportTargetHelper = targetHelper; + } + + public override void AcceptVisitor(TeleportVisitor visitor) + { + visitor.VisitMobileStrongholdTeleport(this); + } + + public override IEnumerable GetTeleportDescriptions() + { + if (_usedTeleportDescription != null) + return new[] { _usedTeleportDescription }; + + return _strongholdTeleportTargetHelper.GetStrongholdTargets(Zone, Eid, TeleportRange, Definition); + } + + public override Dictionary ToDictionary() + { + var result = base.ToDictionary(); + result.Add(k.set, _usedTeleportDescription != null); //the device got fixed to a location + return result; + } + + public void Activate(Player player, TeleportDescription description) + { + if (_usedTeleportDescription != null) + return; + + _usedTeleportDescription = description; + + var gang = player.Gang; + if (gang == null) + return; + + var result = ToDictionary(); + Message.Builder.SetCommand(Commands.TeleportTargetSet).WithData(result).ToCharacters(gang.GetMembers()).Send(); + } + + public override void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) + { + var testlist = _strongholdTeleportTargetHelper.GetStrongholdTargets(Zone, Eid, TeleportRange, Definition).ToList(); + + (testlist.Count == 0).ThrowIfTrue(ErrorCodes.NoTeleportTargetsWereFound); + + // TODO restricting stronghold teleports to alpha use only, address later when new items/strongholds allow for pvp + // Needed to prevent abuse for egressing off beta/gamma + zone.Configuration.IsAlpha.ThrowIfFalse(ErrorCodes.OnlyProtectedZonesAllowed); + + base.CheckDeploymentAndThrow(zone, spawnPosition); + } + } +} diff --git a/src/Perpetuum/Zones/Teleporting/MobileTeleportToZoneMap.cs b/src/Perpetuum/Zones/Teleporting/MobileTeleportToZoneMap.cs new file mode 100644 index 000000000..01e14ffbe --- /dev/null +++ b/src/Perpetuum/Zones/Teleporting/MobileTeleportToZoneMap.cs @@ -0,0 +1,46 @@ +using Perpetuum.Data; +using System.Collections.Generic; + +namespace Perpetuum.Zones.Teleporting +{ + public interface IMobileTeleportToZoneMap + { + List GetDestinationZones(int definition); + } + + public class MobileTeleportZoneMapCache : IMobileTeleportToZoneMap + { + private readonly IDictionary> _cache; + public MobileTeleportZoneMapCache() + { + _cache = MobileTeleportToZoneReader.GetAll(); + } + + public List GetDestinationZones(int definition) + { + _cache.TryGetValue(definition, out List zones); + return zones; + } + } + + public static class MobileTeleportToZoneReader + { + public static IDictionary> GetAll() + { + var dict = new Dictionary>(); + var entries = Db.Query().CommandText("SELECT sourcedefinition, zoneid FROM zoneteleportdevicemap") + .Execute(); + foreach (var entry in entries) + { + var def = entry.GetValue("sourcedefinition"); + var zoneid = entry.GetValue("zoneid"); + dict.AddOrUpdate(def, new List() { zoneid }, (listOfZones) => { + listOfZones.Add(zoneid); + return listOfZones; + }); + } + return dict; + } + + } +} diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs index c65d76376..d17df43d9 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs @@ -3,6 +3,7 @@ using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.Players; +using Perpetuum.Zones.Finders.PositionFinders; namespace Perpetuum.Zones.Teleporting.Strategies { @@ -37,5 +38,33 @@ public void DoTeleport(Player player) Task.Delay(1000).ContinueWith(t => _targetZone.Enter(character,Commands.TeleportUse)); }); } + + [CanBeNull] + public Task DoTeleportAsync(Player player) + { + // get a valid position to teleport to. + var position = new ClosestWalkablePositionFinder(_targetZone, TargetPosition); + if (!position.Find(out Position validPosition)) + { + return null; + } + + player.States.Teleport = true; + Entity.Repository.ForceUpdate(player); + var character = player.Character; + character.ZoneId = _targetZone.Id; + character.ZonePosition = validPosition; + + var task = Task.Delay(10).ContinueWith(t => + { + player.RemoveFromZone(); + player.CurrentSpeed = 0.0; + _targetZone.Enter(character, Commands.TeleportUse); + player.States.InMoveable = false; + player.States.LocalTeleport = false; + }); + + return task; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs index 3aa88de06..17c51a707 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Perpetuum.Log; using Perpetuum.Players; using Perpetuum.Zones.Finders.PositionFinders; @@ -27,7 +28,7 @@ public TeleportWithinZone() public Task DoTeleportAsync(Player player) { var zone = player.Zone; - if (zone == null) + if (zone == null || player.States.LocalTeleport) return null; var finder = new ClosestWalkablePositionFinder(zone, TargetPosition); @@ -48,17 +49,33 @@ public Task DoTeleportAsync(Player player) var task = Task.Delay(TeleportDelay).ContinueWith(t => { - player.RemoveFromZone(); - player.CurrentSpeed = 0.0; - player.AddToZone(zone,validPosition,ZoneEnterType.LocalTeleport); - player.SendInitSelf(); + try + { + player.RemoveFromZone(); + player.CurrentSpeed = 0.0; + player.AddToZone(zone, validPosition, ZoneEnterType.LocalTeleport); + player.SendInitSelf(); - if ( ApplyTeleportSickness ) - player.ApplyTeleportSicknessEffect(); + if (ApplyTeleportSickness) + { + player.ApplyTeleportSicknessEffect(); + } + if (ApplyInvulnerable) + { + player.ApplyInvulnerableEffect(); + } + } + catch (Exception ex) + { + Logger.Exception(ex); + } + finally + { + // mozoghat, fade out + player.States.InMoveable = false; + player.States.LocalTeleport = false; + } - // mozoghat, fade out - player.States.InMoveable = false; - player.States.LocalTeleport = false; }); return task; diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index e82e5d0c2..23299baa5 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using System.Transactions; using Perpetuum.Accounting.Characters; using Perpetuum.Containers; @@ -7,6 +8,7 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Players; using Perpetuum.Services.Channels; +using Perpetuum.Services.Mail; using Perpetuum.Services.Sparks; using Perpetuum.Zones.Training.Reward; @@ -14,8 +16,11 @@ namespace Perpetuum.Zones.Teleporting.Strategies { public class TrainingExitStrategy : ITeleportStrategy { - private const double CHARACTER_START_CREDIT = 20000; + private readonly double CHARACTER_START_CREDIT; + private readonly double NIC_REWARD_PER_LEVEL; private const int MAX_REWARD_LEVEL = 4; + private TimeSpan WAIT_TIME_BEFORE_SENDING_MAIL = TimeSpan.FromSeconds(10); + private TimeSpan WAIT_TIME_BEFORE_SENDING_WELCOME_MESSAGE = TimeSpan.FromSeconds(10); private readonly TeleportDescription _description; private readonly ITrainingRewardRepository _trainingRewardRepository; @@ -26,13 +31,16 @@ public class TrainingExitStrategy : ITeleportStrategy public delegate TrainingExitStrategy Factory(TeleportDescription description); - public TrainingExitStrategy(TeleportDescription description,ITrainingRewardRepository trainingRewardRepository,IChannelManager channelManager,CharacterCleaner characterCleaner,SparkHelper sparkHelper) + public TrainingExitStrategy(TeleportDescription description, ITrainingRewardRepository trainingRewardRepository, IChannelManager channelManager, CharacterCleaner characterCleaner, SparkHelper sparkHelper, GlobalConfiguration globalConfiguration) { _description = description; _trainingRewardRepository = trainingRewardRepository; _channelManager = channelManager; _characterCleaner = characterCleaner; _sparkHelper = sparkHelper; + // Using NIC for new player from globalConfiguration. + CHARACTER_START_CREDIT = globalConfiguration.StartCredit; + NIC_REWARD_PER_LEVEL = globalConfiguration.LevelCredit; } public int TrainingRewardLevel @@ -43,6 +51,9 @@ public int TrainingRewardLevel public void DoTeleport(Player player) { + //Throw if email not confirmed + //player.Character.GetAccount().EmailConfirmed.ThrowIfFalse(ErrorCodes.EmailNotConfirmed); + player.States.Dock = true; var character = player.Character; @@ -53,7 +64,7 @@ public void DoTeleport(Player player) var info = GetCharacterWizardInfo(); - var newCorporation = DefaultCorporation.GetBySchool(info.raceId,info.schoolId); + var newCorporation = DefaultCorporation.GetBySchool(info.raceId, info.schoolId); oldCorporation.RemoveMember(character); newCorporation.AddMember(character, CorporationRole.NotDefined, oldCorporation); @@ -61,7 +72,7 @@ public void DoTeleport(Player player) character.RaceId = info.raceId; character.SchoolId = info.schoolId; character.SparkId = info.sparkId; - character.Credit = CHARACTER_START_CREDIT; + character.Credit = CHARACTER_START_CREDIT + (NIC_REWARD_PER_LEVEL * TrainingRewardLevel); character.DefaultCorporationEid = newCorporation.Eid; //add default extensions @@ -71,9 +82,13 @@ public void DoTeleport(Player player) var sparkToActivate = _sparkHelper.ConvertCharacterWizardSparkIdToSpark(info.sparkId); _sparkHelper.ActivateSpark(character, sparkToActivate); - var dockingBase = newCorporation.GetDockingBase(); + //Grab TM-UAS docking base for New Virginia + var hardCodeTMACorp = DefaultCorporation.GetBySchool(1, 1); + var dockingBase = hardCodeTMACorp.GetDockingBase(); dockingBase.CreateStarterRobotForCharacter(character, true); dockingBase.DockIn(character, TimeSpan.Zero, ZoneExitType.TrainingExitTeleport); + //Set Home Base of character to Docked base + character.HomeBaseEid = dockingBase.Eid; var publicContainer = dockingBase.GetPublicContainerWithItems(character); CreateRewardItems(character, publicContainer); @@ -85,6 +100,17 @@ public void DoTeleport(Player player) _channelManager.LeaveChannel(oldCorporation.ChannelName, character); _channelManager.JoinChannel(newCorporation.ChannelName, character); player.RemoveFromZone(); + + Task.Delay(WAIT_TIME_BEFORE_SENDING_MAIL) + .ContinueWith(task => + { + MailHandler.SendWelcomeMailExitTutorial(character); + }); + Task.Delay(WAIT_TIME_BEFORE_SENDING_WELCOME_MESSAGE) + .ContinueWith(task => + { + ChannelMessageHandler.SendWelcomeMessageExitTutorial(_channelManager, character.Nick); + }); }); } @@ -118,7 +144,7 @@ private struct CharacterWizardInfo public int schoolId; public int sparkId; - public CharacterWizardInfo(int raceId,int schoolId,int majorId,int sparkId) : this() + public CharacterWizardInfo(int raceId, int schoolId, int majorId, int sparkId) : this() { this.majorId = majorId; this.raceId = raceId; @@ -179,4 +205,4 @@ private CharacterWizardInfo HardcodedLookUp(TeleportDescription description) } } -} \ No newline at end of file +} diff --git a/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs b/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs new file mode 100644 index 000000000..36bceb836 --- /dev/null +++ b/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.Teleporting +{ + public class StrongholdTeleportTargetHelper + { + private readonly IZoneManager _zoneManager; + private readonly TeleportDescriptionBuilder.Factory _descriptionBuilderFactory; + private readonly IMobileTeleportToZoneMap _mobileTeleportToZoneMap; + + public StrongholdTeleportTargetHelper(IZoneManager zoneManager, TeleportDescriptionBuilder.Factory descriptionBuilderFactory, IMobileTeleportToZoneMap mobileTeleportToZoneMap) + { + _zoneManager = zoneManager; + _descriptionBuilderFactory = descriptionBuilderFactory; + _mobileTeleportToZoneMap = mobileTeleportToZoneMap; + } + + public IEnumerable GetStrongholdTargets(IZone zone, long sourceTeleportEid, int teleportRange, int mobileDefinition) + { + var result = new List(); + var descriptionId = 0; + var targetZoneIds = _mobileTeleportToZoneMap.GetDestinationZones(mobileDefinition); + var zones = _zoneManager.Zones.OfType().Where(z => targetZoneIds.Contains(z.Id)); + var teleportCols = zones.GetUnits().Where(tc => tc.IsEnabled && tc.Zone != zone); + foreach (var teleportColumn in teleportCols) + { + var builder = _descriptionBuilderFactory(); + builder.SetId(descriptionId++) + .SetType(TeleportDescriptionType.AnotherZone) + .SetSourceTeleport(sourceTeleportEid) + .SetSourceZone(zone) + .SetSourceRange(teleportRange) + .SetTargetZone(teleportColumn.Zone) + .SetTargetTeleport(teleportColumn) + .SetTargetRange(7) + .SetListable(true) + .SetActive(teleportColumn.IsEnabled); + + var td = builder.Build(); + result.Add(td); + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/Teleporting/TeleportVisitor.cs b/src/Perpetuum/Zones/Teleporting/TeleportVisitor.cs index b8aae8202..cec0cb3b1 100644 --- a/src/Perpetuum/Zones/Teleporting/TeleportVisitor.cs +++ b/src/Perpetuum/Zones/Teleporting/TeleportVisitor.cs @@ -5,5 +5,6 @@ public abstract class TeleportVisitor public virtual void VisitTeleport(Teleport teleport) { } public virtual void VisitMobileTeleport(MobileTeleport teleport) { } public virtual void VisitMobileWorldTeleport(MobileWorldTeleport teleport) { } + public virtual void VisitMobileStrongholdTeleport(MobileStrongholdTeleport teleport) { } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Teleporting/TeleportWorldTargetHelper.cs b/src/Perpetuum/Zones/Teleporting/TeleportWorldTargetHelper.cs index 8f9fe9617..1ffaa2952 100644 --- a/src/Perpetuum/Zones/Teleporting/TeleportWorldTargetHelper.cs +++ b/src/Perpetuum/Zones/Teleporting/TeleportWorldTargetHelper.cs @@ -38,7 +38,8 @@ public IEnumerable GetWorldTargets(IZone zone,Position posi .SetTargetZone(teleportColumn.Zone) .SetTargetTeleport(teleportColumn) .SetTargetRange(7) - .SetListable(true); + .SetListable(true) + .SetActive(teleportColumn.IsEnabled); var td = builder.Build(); result.Add(td); diff --git a/src/Perpetuum/Zones/Terrains/AltitudeLayer.cs b/src/Perpetuum/Zones/Terrains/AltitudeLayer.cs index f9b5dca8b..ffbf6ebe4 100644 --- a/src/Perpetuum/Zones/Terrains/AltitudeLayer.cs +++ b/src/Perpetuum/Zones/Terrains/AltitudeLayer.cs @@ -13,6 +13,9 @@ public AltitudeLayer(ushort[] rawData, int width, int height) : base(LayerType.A { } + // Be careful with this call! It will return an integer height + // e.g. the heights 6624 -> 6655 will all return 207 + // For full granularity use the default Layer GetValue public ushort GetAltitude(int x, int y) { // 0 ... 2047 diff --git a/src/Perpetuum/Zones/Terrains/GroundType.cs b/src/Perpetuum/Zones/Terrains/GroundType.cs index ad8be8f5b..108e27306 100644 --- a/src/Perpetuum/Zones/Terrains/GroundType.cs +++ b/src/Perpetuum/Zones/Terrains/GroundType.cs @@ -26,6 +26,7 @@ public enum GroundType : byte type9, type10, type11, + undefined } } diff --git a/src/Perpetuum/Zones/Terrains/Layer.cs b/src/Perpetuum/Zones/Terrains/Layer.cs index 51d2108e6..5ab250c79 100644 --- a/src/Perpetuum/Zones/Terrains/Layer.cs +++ b/src/Perpetuum/Zones/Terrains/Layer.cs @@ -53,6 +53,13 @@ public Layer(LayerType layerType,T[] rawData, int width, int height) : base(laye public unsafe void CopyFromStreamToArea(Stream stream, Area area) { + if (area.Width > Width || area.Height > Height || area.X1 < 0 || area.Y1 < 0) + { + var safeArea = area.Intersect(new Area(0, 0, Width, Height)); + OnAreaUpdated(safeArea); + return; + } + var stride = Width * SizeInBytes; var areaStride = area.Width * SizeInBytes; var dOffset = (area.Y1 * Width + area.X1) * SizeInBytes; diff --git a/src/Perpetuum/Zones/Terrains/Materials/MaterialType.cs b/src/Perpetuum/Zones/Terrains/Materials/MaterialType.cs index 911540aa3..415afebc6 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/MaterialType.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/MaterialType.cs @@ -17,7 +17,7 @@ public enum MaterialType Silgium, //12 Gammaterial, //13 Plants, //14 - EnergyMineral //15 - + EnergyMineral, //15 + FluxOre //16 } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs index 5e1621dff..a3d8805f4 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.Teleporting; namespace Perpetuum.Zones.Terrains.Materials.Minerals.Generators { @@ -19,13 +22,15 @@ protected MineralNodeGeneratorBase(IZone zone) public int MaxTiles { protected get; set; } public int TotalAmount { private get; set; } public double MinThreshold { private get; set; } + public MaterialType Material { private get; set; } + public MineralNode Generate(MineralLayer layer) { var startPosition = FindStartPosition(layer); var noise = GenerateNoise(startPosition); var normalizedNoise = NormalizeNoise(noise); - var node = CreateMineralNode(layer,normalizedNoise); + var node = CreateMineralNode(layer, normalizedNoise); return node; } @@ -37,13 +42,13 @@ private Dictionary NormalizeNoise(Dictionary noise foreach (var kvp in noise) { - result[kvp.Key] = (kvp.Value/max).Clamp().Normalize(MinThreshold,1.0); + result[kvp.Key] = (kvp.Value / max).Clamp().Normalize(MinThreshold, 1.0); } return result; } - - protected abstract Dictionary GenerateNoise(Position startPosition); + + protected abstract Dictionary GenerateNoise(Position startPosition); protected bool IsValid(Point location) { @@ -72,7 +77,7 @@ protected bool IsValid(Point location) return true; } - private MineralNode CreateMineralNode(MineralLayer layer,Dictionary tiles) + private MineralNode CreateMineralNode(MineralLayer layer, Dictionary tiles) { int minx = int.MaxValue, miny = int.MaxValue, maxx = 0, maxy = 0; @@ -97,12 +102,40 @@ private MineralNode CreateMineralNode(MineralLayer layer,Dictionary().WithinRange2D(location.ToPosition(), dist).Any()) + return true; + + if (_zone.Units.OfType().WithinRange2D(location.ToPosition(), dist).Any()) + return true; + return false; + } + + private static double KeepOutDist(MaterialType material) + { + switch (material) + { + case MaterialType.Epriton: + return DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN; + case MaterialType.FluxOre: + return DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN * 2.0; + default: + return 0; + } + } + + private static bool IsKeepOutMaterial(MaterialType material) + { + return material == MaterialType.Epriton || material == MaterialType.FluxOre; + } + private Position FindStartPosition(MineralLayer layer) { var finder = new RandomPassablePositionFinder(_zone); @@ -112,7 +145,16 @@ private Position FindStartPosition(MineralLayer layer) if (!finder.Find(out Position startPosition)) continue; - if ( !IsValid(startPosition) ) + // Check keepout distances for special ore types + if (IsKeepOutMaterial(Material)) + { + if (IsInRangeOfBaseOrTeleports(startPosition, KeepOutDist(Material))) + { + continue; + } + } + + if (!IsValid(startPosition)) continue; var n = layer.GetNearestNode(startPosition); @@ -120,7 +162,7 @@ private Position FindStartPosition(MineralLayer layer) return startPosition; var d = n.Area.Distance(startPosition); - if (d < Radius*2) + if (d < Radius * 2) { // ha tul kozel van akkor keresunk ujat continue; diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/GravelLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/GravelLayer.cs index f0741d9af..b8083c04b 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/GravelLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/GravelLayer.cs @@ -3,6 +3,7 @@ using System.Drawing; using System.IO; using Perpetuum.IO; +using Perpetuum.Services.EventServices; using Perpetuum.Zones.Terrains.Materials.Minerals.Generators; namespace Perpetuum.Zones.Terrains.Materials.Minerals @@ -91,8 +92,8 @@ public List GetAll() public class GravelLayer : OreLayer { - public GravelLayer(int width, int height, IMineralConfiguration configuration,GravelRepository repository) - : base(width,height,configuration, repository,MineralNodeGeneratorFactory.None) + public GravelLayer(int width, int height, IMineralConfiguration configuration, GravelRepository repository) + : base(width, height, configuration, repository, MineralNodeGeneratorFactory.None, null) { } diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/LiquidLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/LiquidLayer.cs index ae1c4455b..4828463b5 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/LiquidLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/LiquidLayer.cs @@ -1,11 +1,12 @@ +using Perpetuum.Services.EventServices; using Perpetuum.Zones.Terrains.Materials.Minerals.Generators; namespace Perpetuum.Zones.Terrains.Materials.Minerals { public class LiquidLayer : MineralLayer { - public LiquidLayer(int width, int height, IMineralConfiguration configuration, MineralNodeRepository nodeRepository, IMineralNodeGeneratorFactory nodeGeneratorFactory) - : base(width,height,configuration, nodeRepository,nodeGeneratorFactory) + public LiquidLayer(int width, int height, IMineralConfiguration configuration, IMineralNodeRepository nodeRepository, IMineralNodeGeneratorFactory nodeGeneratorFactory, EventListenerService eventListenerService) + : base(width, height, configuration, nodeRepository, nodeGeneratorFactory, eventListenerService) { } diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs index 4d16ca556..6418ce834 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs @@ -3,6 +3,9 @@ using System.Collections.Immutable; using System.Drawing; using Perpetuum.Log; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Timers; using Perpetuum.Zones.Terrains.Materials.Minerals.Actions; using Perpetuum.Zones.Terrains.Materials.Minerals.Generators; @@ -14,12 +17,14 @@ public abstract class MineralLayer : ActiveLayer,IMaterialLayer private readonly IMineralNodeRepository _nodeRepository; private readonly IMineralNodeGeneratorFactory _nodeGeneratorFactory; private MineralNode[] _nodes = new MineralNode[0]; + private readonly EventListenerService _eventChannel; - protected MineralLayer(int width,int height,IMineralConfiguration configuration,IMineralNodeRepository nodeRepository,IMineralNodeGeneratorFactory nodeGeneratorFactory) : base(LayerType.Material,width,height) + protected MineralLayer(int width,int height,IMineralConfiguration configuration,IMineralNodeRepository nodeRepository,IMineralNodeGeneratorFactory nodeGeneratorFactory, EventListenerService eventListenerService) : base(LayerType.Material,width,height) { _configuration = configuration; _nodeRepository = nodeRepository; _nodeGeneratorFactory = nodeGeneratorFactory; + _eventChannel = eventListenerService; } public IMineralNodeRepository NodeRepository => _nodeRepository; @@ -71,6 +76,7 @@ public void GenerateNewNode() generator.MaxTiles = _configuration.MaxTilesPerNode; generator.TotalAmount = _configuration.TotalAmountPerNode; generator.MinThreshold = _configuration.MinThreshold; + generator.Material = _configuration.Type; RunAction(new GenerateMineralNode(generator)); } @@ -134,7 +140,7 @@ public void AddNode(MineralNode node) var result = new List(nodes) {node}; return result.ToArray(); }); - + node.Decrease += OnNodeDecrease; node.Updated += OnNodeUpdated; node.Expired += OnNodeExpired; } @@ -157,6 +163,26 @@ public void RemoveNode(MineralNode node) }); } + private bool IsNPCSpawningOre(MineralNode node) + { + return node.Type == MaterialType.FluxOre; + } + + // Timer to buffer excessive decrease messages on ore mining + private readonly TimeKeeper _time = new TimeKeeper(TimeSpan.FromSeconds(5)); + private void OnNodeDecrease(MineralNode node) + { + if (_time.Expired) + { + if (IsNPCSpawningOre(node)) + { + var msg = new OreNpcSpawnMessage(node, Configuration.ZoneId, OreNodeState.Updated); + _eventChannel.PublishMessage(msg); + } + _time.Reset(); + } + } + private void OnNodeUpdated(MineralNode node) { RunAction(new SaveMineralNode(node)); @@ -164,6 +190,11 @@ private void OnNodeUpdated(MineralNode node) private void OnNodeExpired(MineralNode node) { + if (IsNPCSpawningOre(node)) + { + var msg = new OreNpcSpawnMessage(node, Configuration.ZoneId, OreNodeState.Removed); + _eventChannel.PublishMessage(msg); + } RunAction(new DeleteMineralNode(node)); } @@ -223,7 +254,7 @@ public bool HasMineral(Point location) public MineralNode CreateNode(Area area) { - return new MineralNode(Type,area); + return new MineralNode(Type, area); } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs index ac432dc37..9180150d3 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs @@ -50,6 +50,13 @@ public uint[] Values get { return _values; } } + public event MineralNodeEventHandler Decrease; + + private void OnDecrease() + { + Decrease?.Invoke(this); + } + public event MineralNodeEventHandler Updated; private void OnUpdated() @@ -92,6 +99,7 @@ public bool HasValue(int x, int y) public uint DecreaseValue(Point p, uint value) { + OnDecrease(); return DecreaseValue(p.X, p.Y, value); } @@ -229,5 +237,26 @@ public Point GetNearestMineralPosition(Point p) return nearest; } + + public override bool Equals(object obj) + { + return Equals(obj as MineralNode); + } + + public bool Equals(MineralNode other) + { + return other != null && ReferenceEquals(this, other) || other.Type == Type && other.Area == _area; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 23; + hash = hash * 31 + Type.GetHashCode(); + hash = hash * 31 + Area.GetHashCode(); + return hash; + } + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/OreLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/OreLayer.cs index 9f0ebad68..8629c2733 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/OreLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/OreLayer.cs @@ -1,11 +1,12 @@ +using Perpetuum.Services.EventServices; using Perpetuum.Zones.Terrains.Materials.Minerals.Generators; namespace Perpetuum.Zones.Terrains.Materials.Minerals { public class OreLayer : MineralLayer { - public OreLayer(int width, int height, IMineralConfiguration configuration, IMineralNodeRepository nodeRepository, IMineralNodeGeneratorFactory nodeGeneratorFactory) - : base(width,height,configuration, nodeRepository,nodeGeneratorFactory) + public OreLayer(int width, int height, IMineralConfiguration configuration, IMineralNodeRepository nodeRepository, IMineralNodeGeneratorFactory nodeGeneratorFactory, EventListenerService eventListenerService = null) + : base(width, height, configuration, nodeRepository, nodeGeneratorFactory, eventListenerService) { } diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/Harvesters/PlantHarvester.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/Harvesters/PlantHarvester.cs index dc23c68a4..406046b76 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/Harvesters/PlantHarvester.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/Harvesters/PlantHarvester.cs @@ -34,18 +34,16 @@ public IEnumerable HarvestPlant(Position position) if (pi.material <= 0) { - //elfogyott a material a novenybol - //megoljuk erobol pi.Clear(); - pi.state = 1; //force kidoles :) - //kill the blocking as well + pi.state = 1; _zone.Terrain.ClearPlantBlocking(position); } - //resulting material var quantity = GetHarvestedAmountPerCycle(plantRule); + result.Add(new ItemInfo(plantRule.FruitDefinition, quantity)); result.AddRange(_rareMaterialHandler.GenerateRareMaterials(plantRule.FruitDefinition)); + return pi; }); diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index aa28008b6..dde5a74d1 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -1,10 +1,10 @@ +using Perpetuum.Log; +using Perpetuum.Threading.Process; +using Perpetuum.Timers; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Perpetuum.Log; -using Perpetuum.Threading.Process; -using Perpetuum.Timers; namespace Perpetuum.Zones.Terrains.Materials.Plants { @@ -13,7 +13,7 @@ public interface IPlantHandler : IProcess TimeSpan RenewSpeed { set; } Area WorkArea { set; } PlantScannerMode ScannerMode { set; } - IDictionary GetInfoDictionary(); + IDictionary GetInfoDictionary(); } public class PlantHandler : IPlantHandler @@ -21,10 +21,14 @@ public class PlantHandler : IPlantHandler public delegate IPlantHandler Factory(IZone zone); private const int AREA_SIZE = 32; + // Number of hours a full pass of plant regen should take + private readonly double FULL_PLANT_REGEN_PASS = 8; private readonly IZone _zone; - private readonly TimeSpan _natureSleepAmount = TimeSpan.FromSeconds(7); + // Default value of 7s below. These get changed immediately but are left just in case. + private TimeSpan _natureSleepAmount = TimeSpan.FromSeconds(7); private IntervalTimer _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(7)); + private PlantScannerMode _scannerMode = PlantScannerMode.Paused; private bool _zoneFinished; @@ -41,8 +45,13 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas + float _total_area = zone.Size.Width / (float)AREA_SIZE * (zone.Size.Height / (float)AREA_SIZE); + TimeSpan refreshRate = zone.Configuration.PlantsGrowthTimerOverrideMin.HasValue + ? TimeSpan.FromMinutes(zone.Configuration.PlantsGrowthTimerOverrideMin.Value) + : TimeSpan.FromHours(FULL_PLANT_REGEN_PASS / _total_area); + _plantsTimer = new IntervalTimer(refreshRate); + _natureSleepAmount = refreshRate; WorkArea = zone.Size.ToArea(); - ScannerMode = PlantScannerMode.Scanner; ResetState(); } @@ -59,17 +68,21 @@ public void Stop() public void Update(TimeSpan time) { - _plantsTimer.Update(time); + _ = _plantsTimer.Update(time); - if (!_plantsTimer.Passed) + if (!_plantsTimer.Passed) + { return; + } _plantsTimer.Reset(); - if ( Interlocked.CompareExchange(ref _isInProcess,1,0) == 1) + if (Interlocked.CompareExchange(ref _isInProcess, 1, 0) == 1) + { return; + } - Task.Run(() => ProcessPlants()).ContinueWith(t => + _ = Task.Run(() => ProcessPlants()).ContinueWith(t => { _isInProcess = 0; _stopSignal = false; @@ -113,7 +126,11 @@ private void FullMapScan() //do full map in one round while (!_zoneFinished) { - if (_stopSignal) return; + if (_stopSignal) + { + return; + } + ScanZone(); } @@ -127,12 +144,12 @@ private void ScanZone() { if (_stopSignal) { - Logger.Info("Planthandler STOP SIGNAL received. zone:"+ _zone.Id); + Logger.Info("Planthandler STOP SIGNAL received. zone:" + _zone.Id); return; } - var area = GetNewArea(); -#if (DEBUG) + Area area = GetNewArea(); +#if DEBUG if (ScannerMode == PlantScannerMode.Scanner && area.X1 == 0 && area.Y1 == 0) { Logger.Info("plant scan starts on zone:" + _zone.Id); @@ -155,7 +172,7 @@ private void CorrectZone() //do full map in one round while (!_zoneFinished) { - var area = GetNewArea(); + Area area = GetNewArea(); _zone.UpdateNatureCube(area, cube => cube.CorrectOnly()); } } @@ -206,7 +223,7 @@ private void SetSpeedHigh() public TimeSpan RenewSpeed { - set { _plantsTimer = new IntervalTimer(value); } + set => _plantsTimer = new IntervalTimer(value); } private void ResetState() @@ -222,13 +239,15 @@ public PlantScannerMode ScannerMode { set { - if ( _scannerMode == value ) + if (_scannerMode == value) + { return; + } - var preValue = _scannerMode; + PlantScannerMode preValue = _scannerMode; _scannerMode = value; - Logger.Info("Plant scanner mode is set " + preValue + " -> " + _scannerMode +" on " + _zone.Id); + Logger.Info("Plant scanner mode is set " + preValue + " -> " + _scannerMode + " on " + _zone.Id); ResetState(); @@ -266,13 +285,13 @@ public PlantScannerMode ScannerMode _zoneFinished = false; } - get { return _scannerMode; } + get => _scannerMode; } public IDictionary GetInfoDictionary() { - var result = new Dictionary + Dictionary result = new Dictionary { {k.mode,_scannerMode.ToString()}, {k.area, WorkArea}, diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs index 733ca0613..6cf003cb0 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs @@ -23,6 +23,16 @@ public void Clear() material = 0; } + public void ClearGroundType() + { + SetGroundType(GroundType.undefined); + } + + public void SetGroundType(GroundType type) + { + groundType = type; + } + public void SetPlant(byte newState, PlantType newPlantType) { state = newState; diff --git a/src/Perpetuum/Zones/Terrains/NatureCube.cs b/src/Perpetuum/Zones/Terrains/NatureCube.cs index 07cbee7bc..5b19d84ec 100644 --- a/src/Perpetuum/Zones/Terrains/NatureCube.cs +++ b/src/Perpetuum/Zones/Terrains/NatureCube.cs @@ -456,6 +456,8 @@ private void SpawnPlants() var currentAmount = kvp.Value; var rule = _zone.Configuration.PlantRules.GetPlantRule(currentPlantType); + if (rule == null) + continue; if (rule.HasBlockingState) { diff --git a/src/Perpetuum/Zones/Terrains/TerrainControlInfo.cs b/src/Perpetuum/Zones/Terrains/TerrainControlInfo.cs index 733db3695..f621b3604 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainControlInfo.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainControlInfo.cs @@ -94,7 +94,7 @@ private bool HasFlags(TerrainControlFlags flags) return ((int)_flags & ((int)flags)) > 0; } - private void SetFlags(TerrainControlFlags f,bool value) + public void SetFlags(TerrainControlFlags f,bool value) { _flags = value ? (TerrainControlFlags)((ushort)_flags | (ushort)f) : (TerrainControlFlags)((ushort)_flags & ~((ushort)f)); } diff --git a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs index 5be74bc6f..77b5293f8 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -90,6 +92,22 @@ public static int CountPlantsInArea(this IZone zone, PlantType plantType, Area a return counter; } + public static List GetPlantPositionsInArea(this IZone zone, PlantType plantType, Area area) + { + var result = new List(); + + zone.ForEachAreaInclusive(area, (x, y) => + { + var pi = zone.Terrain.Plants.GetValue(x, y); + if (pi.type == plantType) + { + result.Add(new Position(x, y)); + } + }); + + return result; + } + public static void ForEachAll(this IZone zone, Action action) { for (var y = 0; y < zone.Size.Height; y++) @@ -115,10 +133,10 @@ public static void ForEachAreaInclusive(this IZone zone, Area area, Action d.value); + var damage = damageInfo.CalculatePlantDamages(); var rangeFar = (int)damageInfo.Range; double originX = damageInfo.sourcePosition.intX; @@ -138,12 +156,12 @@ public static void DamageToPlantOnArea(this IZone zone,DamageInfo damageInfo) } } - public static void DamageToPlantOnArea(this IZone zone,Area area,double damage) + public static void DamageToPlantOnArea(this IZone zone, Area area, double damage) { - var w = area.Width/2; - var h = area.Height/2; + var w = area.Width / 2; + var h = area.Height / 2; - var maxd = w*w + h*h; + var maxd = w * w + h * h; var cx = area.Center.X; var cy = area.Center.Y; @@ -155,10 +173,10 @@ public static void DamageToPlantOnArea(this IZone zone,Area area,double damage) var dx = cx - x; var dy = cy - y; - var d = dx*dx + dy*dy; - var m = 1.0 - ((double)d/maxd); - - var dmg = damage*m; + var d = dx * dx + dy * dy; + var m = 1.0 - ((double)d / maxd); + + var dmg = damage * m; zone.DamageToPlant(x, y, dmg); } } @@ -181,22 +199,17 @@ public static void DamageToPlant(this IZone zone,int x, int y, double damage) if (FastRandom.NextDouble() < 0.3) { - damage = damage/3; + damage /= 3.0; } var damageInt = (int)(damage * plantRule.DamageScale).Clamp(int.MinValue, int.MaxValue); if (damageInt <= 0) { - -#if DEBUG - //Console.WriteLine(plantRule.Type + " low damage"); -#endif + Logger.DebugInfo(plantRule.Type + " low damage"); return; } -#if DEBUG - //Console.WriteLine(plantRule.Type + " damage " + damageInt); -#endif + Logger.DebugInfo(plantRule.Type + " damage " + damageInt); int currentHealth = currPlant.health; currentHealth -= damageInt; @@ -304,6 +317,38 @@ public static MineralLayer GetMineralLayerOrThrow(this ITerrain terrain, Materia return terrain.GetMaterialLayer(type).ThrowIfNotType(ErrorCodes.NoSuchMineralOnZone); } + public static MaterialType GetMaterialTypeAtPosition(this ITerrain terrain, Position position) + { + MaterialType[] materials = Enum.GetValues(typeof(MaterialType)) as MaterialType[]; + + var layer = materials + .Select(x => terrain.GetMaterialLayer(x)) + .Where(x => x is MineralLayer) + .FirstOrDefault(x => (x as MineralLayer).Nodes.Any(y => y.Area.Contains(position))); + + return layer == null + ? MaterialType.Undefined + : (layer as MineralLayer).Type; + } + + public static MaterialType[] GetAvailableMineralTypes(this ITerrain terrain) + { + MaterialType[] materials = Enum.GetValues(typeof(MaterialType)) as MaterialType[]; + + return materials + .Where(x => terrain.GetMaterialLayer(x) != null) + .ToArray(); + } + + public static PlantType[] GetAvailablePlantTypes(this ITerrain terrain) + { + PlantType[] materials = Enum.GetValues(typeof(PlantType)) as PlantType[]; + + return materials + .Where(x => terrain.Plants.RawData.Any(y => y.type == x && y.material > 0)) + .ToArray(); + } + private const int GZIP_THRESHOLD = 260; [CanBeNull] diff --git a/src/Perpetuum/Zones/Terrains/TerrainUpdateNotifier.cs b/src/Perpetuum/Zones/Terrains/TerrainUpdateNotifier.cs index 64e16bc7a..55274c99a 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainUpdateNotifier.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainUpdateNotifier.cs @@ -166,7 +166,7 @@ private void DequeueNewUpdates() } var cell = _grid.GetCell(info.Position); - cell.Updates.AddLast(info); + cell?.Updates.AddLast(info); } } finally diff --git a/src/Perpetuum/Zones/WeatherInfo.cs b/src/Perpetuum/Zones/WeatherInfo.cs deleted file mode 100644 index 669d33776..000000000 --- a/src/Perpetuum/Zones/WeatherInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Perpetuum.Timers; - -namespace Perpetuum.Zones -{ - public class WeatherInfo - { - private readonly int _current; - public int Next { get; private set; } - - private readonly TimeTracker _timer; - - public WeatherInfo(int current, int next,TimeSpan duration) - { - _current = current; - Next = next; - _timer = new TimeTracker(duration); - } - - public bool Update(TimeSpan elapsed) - { - _timer.Update(elapsed); - return _timer.Expired; - } - - public Packet CreateUpdatePacket() - { - var packet = new Packet(ZoneCommand.Weather); - packet.AppendByte(0); // ez lesz majd a - packet.AppendByte((byte)_current); - packet.AppendByte((byte)Next); - packet.AppendLong((long)_timer.Elapsed.TotalMilliseconds); - packet.AppendLong((long)_timer.Duration.TotalMilliseconds); - return packet; - } - } -} \ No newline at end of file diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 21188bb5c..9858f5e82 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -15,14 +15,19 @@ using Perpetuum.Network; using Perpetuum.Players; using Perpetuum.Services.HighScores; +using Perpetuum.Services.Relics; using Perpetuum.Services.RiftSystem; using Perpetuum.Services.Sessions; +using Perpetuum.Services.Strongholds; +using Perpetuum.Services.Weather; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Blobs; using Perpetuum.Zones.Decors; +using Perpetuum.Zones.Effects.ZoneEffects; using Perpetuum.Zones.Environments; +using Perpetuum.Zones.LandMines; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; using Perpetuum.Zones.PBS; @@ -36,7 +41,7 @@ namespace Perpetuum.Zones { - public abstract class Zone : Threading.Process.Process,IZone + public abstract class Zone : Threading.Process.Process, IZone { private ImmutableHashSet _sessions = ImmutableHashSet.Empty; private ImmutableDictionary _units = ImmutableDictionary.Empty; @@ -61,10 +66,18 @@ public abstract class Zone : Threading.Process.Process,IZone public TerraformHandler TerraformHandler { get; set; } public IZoneUnitService UnitService { get; set; } public MiningLogHandler MiningLogHandler { get; set; } + public HarvestLogHandler HarvestLogHandler { get; set; } public ZoneSession.Factory ZoneSessionFactory { get; set; } + public IZoneEffectHandler ZoneEffectHandler { get; set; } [CanBeNull] - public RiftManager RiftManager { private get; set; } + public IRiftManager RiftManager { private get; set; } + + [CanBeNull] + public IRelicManager RelicManager { get; set; } + + [CanBeNull] + public IStrongholdPlayerStateManager PlayerStateManager { get; set; } public IZoneEnterQueueService EnterQueueService { get; set; } @@ -74,13 +87,18 @@ public abstract class Zone : Threading.Process.Process,IZone public TcpListener Listener { get; set; } - protected Zone(ISessionManager sessionManager,IGangManager gangManager) + private readonly SessionlessPlayerTimeout _sessionlessPlayerTimeout; + public bool IsLayerEditLocked { get; set; } + + protected Zone(ISessionManager sessionManager, IGangManager gangManager) { + IsLayerEditLocked = true; sessionManager.CharacterDeselected += OnCharacterDeselected; _gangManager = gangManager; _gangManager.GangMemberJoined += OnGangMemberJoined; _gangManager.GangMemberRemoved += OnGangMemberRemoved; _gangManager.GangDisbanded += OnGangDisbanded; + _sessionlessPlayerTimeout = new SessionlessPlayerTimeout(this); } private void OnCharacterDeselected(ISession session, Character character) @@ -94,18 +112,22 @@ private void OnCharacterDeselected(ISession session, Character character) public override void Start() { Listener.Start(OnConnectionAccepted); + RelicManager?.Start(); } public override void Stop() { Listener.Stop(); + //Emit stop signal to child services + RelicManager?.Stop(); + // players SaveUnitsToDb(); SaveUnitsToDb(); SaveUnitsToDb(); SaveUnitsToDb(); - SaveUnitsToDb(); + SaveUnitsToDb(); } private void OnGangMemberJoined(Gang gang, Character character) @@ -145,6 +167,8 @@ private void UpdateSessions(TimeSpan time) { session.Update(time); } + + _sessionlessPlayerTimeout.Update(time); } [CanBeNull] @@ -183,10 +207,15 @@ public void AddUnit(Unit unit) return; if (unit is Player player) + { ImmutableInterlocked.TryAdd(ref _players, player.Eid, player); + StrongholdPlayerStateManager.OnPlayerAddToZone(this, player); + } unit.Updated += OnUnitUpdated; unit.Dead += OnUnitDead; + + ZoneEffectHandler.OnEnterZone(unit); Logger.Info($"Unit entered to zone. zone:{Id} eid = {unit.InfoString} ({unit.CurrentPosition})"); } @@ -214,7 +243,10 @@ public void RemoveUnit(Unit unit) return; if (u is Player player) + { ImmutableInterlocked.TryRemove(ref _players, player.Eid, out player); + PlayerStateManager?.OnPlayerExitZone(player); + } u.Updated -= OnUnitUpdated; Logger.Info($"Unit exited from zone. zone:{Id} eid = {u.InfoString} ({u.CurrentPosition})"); @@ -300,7 +332,9 @@ public override void Update(TimeSpan time) UpdateUnits(time); RiftManager?.Update(time); + RelicManager?.Update(time); MiningLogHandler.Update(time); + HarvestLogHandler.Update(time); MeasureUpdate(time); } diff --git a/src/Perpetuum/Zones/ZoneConfiguration.cs b/src/Perpetuum/Zones/ZoneConfiguration.cs index f850fe21c..7c386dd2d 100644 --- a/src/Perpetuum/Zones/ZoneConfiguration.cs +++ b/src/Perpetuum/Zones/ZoneConfiguration.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; -using System.Drawing; -using Perpetuum.Data; +using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Zones.Terrains.Materials.Plants; +using System.Collections.Generic; +using System.Drawing; namespace Perpetuum.Zones { @@ -17,7 +17,7 @@ public class ZoneConfigurationReader : IZoneConfigurationReader private readonly GlobalConfiguration _globalConfiguration; private readonly PlantRuleLoader _plantRuleLoader; - public ZoneConfigurationReader(GlobalConfiguration globalConfiguration,PlantRuleLoader plantRuleLoader) + public ZoneConfigurationReader(GlobalConfiguration globalConfiguration, PlantRuleLoader plantRuleLoader) { _globalConfiguration = globalConfiguration; _plantRuleLoader = plantRuleLoader; @@ -25,23 +25,23 @@ public ZoneConfigurationReader(GlobalConfiguration globalConfiguration,PlantRule public IEnumerable GetAll() { - var records = Db.Query().CommandText("select * from zones where enabled = 1").Execute(); + List records = Db.Query().CommandText("select * from zones where enabled = 1").Execute(); - var result = new List(); + List result = new List(); - var port = _globalConfiguration.ListenerPort + 1; + int port = _globalConfiguration.ListenerPort + 1; - - foreach (var record in records) + + foreach (System.Data.IDataRecord record in records) { - var x = record.GetValue("x"); - var y = record.GetValue("y"); - var w = record.GetValue("width"); - var h = record.GetValue("height"); - var id = record.GetValue("id"); + int x = record.GetValue("x"); + int y = record.GetValue("y"); + int w = record.GetValue("width"); + int h = record.GetValue("height"); + int id = record.GetValue("id"); - var config = new ZoneConfiguration + ZoneConfiguration config = new ZoneConfiguration { Id = id, WorldPosition = new Point(x, y), @@ -61,6 +61,11 @@ public IEnumerable GetAll() MaxPlayers = 10000, MaxDockingBase = record.GetValue("maxdockingbase"), PlantAltitudeScale = record.GetValue("plantaltitudescale"), + Note = record.GetValue("note"), + + TimeLimitMinutes = record.GetValue("timeLimitMinutes"), + PBSTechLimit = record.GetValue("pbsTechLimit") ?? 0, + PlantsGrowthTimerOverrideMin = record.GetValue("PlantsGrowthTimerOverrideMin"), }; config.PlantRules = _plantRuleLoader.LoadPlantRulesWithOverrides(config.plantRuleSetId); @@ -82,7 +87,7 @@ public sealed class ZoneConfiguration {2, DefinitionNames.PUBLIC_TELEPORT_COLUMN_NUIMQOL}, {3, DefinitionNames.PUBLIC_TELEPORT_COLUMN_THELODICA} }; - + public int plantRuleSetId; public int Id { get; set; } @@ -98,10 +103,16 @@ public sealed class ZoneConfiguration public int MaxDockingBase { get; set; } public double PlantAltitudeScale { private get; set; } public int RaceId { get; set; } + public string Note { get; set; } - public string ListenerAddress { get; set; } + public string ListenerAddress { get; set; } = "127.0.0.1"; public int ListenerPort { get; set; } + public int? TimeLimitMinutes { get; set; } + public int PBSTechLimit { get; set; } + + public int? PlantsGrowthTimerOverrideMin { get; set; } + public ZoneType Type { get; set; } public Dictionary ToDictionary() @@ -126,7 +137,8 @@ public Dictionary ToDictionary() {k.waterLevel, WaterLevel}, {k.type, (int) Type}, {k.sparkCost, SparkCost}, - {k.maxDockingBase, MaxDockingBase} + {k.maxDockingBase, MaxDockingBase}, + {k.note, Note } }; } @@ -139,7 +151,7 @@ public EntityDefault TeleportColumn { get { - var name = _raceIDToTeleport.GetOrDefault(RaceId, DefinitionNames.PUBLIC_TELEPORT_COLUMN_PELISTAL); + string name = _raceIDToTeleport.GetOrDefault(RaceId, DefinitionNames.PUBLIC_TELEPORT_COLUMN_PELISTAL); return EntityDefault.GetByName(name); } } @@ -157,10 +169,10 @@ public ZoneStorage GetStorage() public bool IsAlpha => Protected; - public bool IsBeta => (!Protected && !Terraformable); + public bool IsBeta => !Protected && !Terraformable; public bool IsGamma => Terraformable; } } - + diff --git a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs index de593ee1a..a1a039815 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs @@ -4,6 +4,7 @@ using Perpetuum.Builders; using Perpetuum.Groups.Gangs; using Perpetuum.Players; +using Perpetuum.Zones.RemoteControl; namespace Perpetuum.Zones { @@ -33,6 +34,19 @@ public static IEnumerable GetGangMembers(this IZone zone, Gang gang) return gang == null ? new Player[0] : zone.Players.Where(player => player.Gang == gang); } + public static IEnumerable GetAlliedTurretsByPlayers(this IZone zone, IEnumerable players) + { + return !players.Any() + ? new RemoteControlledCreature[0] + : zone.Units + .Where(x => x is RemoteControlledCreature) + .Join( + players, + turret => ((RemoteControlledCreature)turret).CommandRobot, + player => player, + (turret, player) => (RemoteControlledCreature)turret); + } + public static void SendPacketToGang(this IZone zone, Gang gang, IBuilder packetBuidler, long exceptMemberEid = 0L) { var packet = packetBuidler.Build(); diff --git a/src/Perpetuum/Zones/ZoneExtensions.Npc.cs b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs index ccfce3928..2ad2359b5 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Npc.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs @@ -1,5 +1,7 @@ -using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.NpcSystem.Flocks; using Perpetuum.Zones.NpcSystem.Presences; +using System; namespace Perpetuum.Zones { @@ -14,5 +16,17 @@ public static DynamicPresence AddDynamicPresenceToPosition(this IZone zone,int p zone.PresenceManager.AddPresence(presence); return presence; } + + public static DynamicPresenceExtended AddDynamicPresenceToPosition(this IZone zone, int presenceID, Position homePos, Position startPos, TimeSpan timeSpan) + { + var presence = zone.PresenceManager.CreatePresence(presenceID).ThrowIfNotType(ErrorCodes.ItemNotFound); + presence.LifeTime = timeSpan; + presence.DynamicPosition = homePos; + presence.SpawnLocation = startPos; + presence.LoadFlocks(); + presence.Flocks.SpawnAllMembers(); + zone.PresenceManager.AddPresence(presence); + return presence; + } } } diff --git a/src/Perpetuum/Zones/ZoneExtensions.Unit.cs b/src/Perpetuum/Zones/ZoneExtensions.Unit.cs index 650323d61..76e333868 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Unit.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Unit.cs @@ -9,6 +9,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Blobs; using Perpetuum.Zones.Gates; +using Perpetuum.Zones.LandMines; using Perpetuum.Zones.PBS; using Perpetuum.Zones.PBS.DockingBases; using Perpetuum.Zones.ProximityProbes; @@ -41,70 +42,23 @@ public static void UpdateUnitRelations(this IZone zone, Unit sourceUnit) public static Dictionary GetBuildingsDictionaryForCharacter(this IZone zone, Character character) { - long corporationEid; - CorporationRole role; - Corporation.GetCorporationEidAndRoleFromSql(character, out corporationEid, out role); - var count = 0; var buildingsDict = zone.Units - .NotOf().Types().Type() + .NotOf().Types().Type() .Where(u => { - - var pbsDockingBase = u as PBSDockingBase; - if (pbsDockingBase != null) + if (u is PBSDockingBase pbsDockingBase) { - //pbs docking base was found - - if (pbsDockingBase.DockingBaseMapVisibility == PBSDockingBaseVisibility.open) - return true; - - if (pbsDockingBase.DockingBaseMapVisibility == PBSDockingBaseVisibility.corporation) - { - if (u.Owner == corporationEid) - { - return true; //everybody within corporation - } - - return false; - - } - - if (pbsDockingBase.DockingBaseMapVisibility == PBSDockingBaseVisibility.hidden) - { - if (u.Owner == corporationEid) - { - return role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO, CorporationRole.viewPBS); - - } - - return false; - } - - - return false; //fallback, unknown visibility + return pbsDockingBase.IsVisible(character); } - - var pbs = u as IPBSObject; - if (pbs != null) + else if (u is IPBSObject pbs) { return false; //pbs cuccok nem latszodnak } - - var gate = u as Gate; - if (gate != null) + else if (u is Gate gate) { - if (gate.Owner == corporationEid) - { - //our gate - return role.IsAnyRole(CorporationRole.CEO, CorporationRole.DeputyCEO, CorporationRole.viewPBS); - - } - - return false; + return gate.IsVisible(character); } - - return true; }) .ToDictionary(unit => "b" + count++, unit => unit.ToDictionary()); @@ -126,7 +80,7 @@ public static IEnumerable GetTeleportColumns(this IZone zone) public static IEnumerable GetStaticUnits(this IZone zone) { - return zone.Units.NotOf().Types(); + return zone.Units.NotOf().Types(); } [NotNull] diff --git a/src/Perpetuum/Zones/ZoneExtensions.cs b/src/Perpetuum/Zones/ZoneExtensions.cs index 5a6707f84..55a8e0067 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; using Perpetuum.Builders; using Perpetuum.Groups.Corporations; using Perpetuum.IO; @@ -12,6 +5,13 @@ using Perpetuum.Modules.Weapons; using Perpetuum.Units; using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; namespace Perpetuum.Zones { @@ -23,9 +23,9 @@ public interface ILayerFileIO public static class LayerFileIOExtensions { - public static T[] Load(this ILayerFileIO dataIO,IZone zone,LayerType layerType) where T : struct + public static T[] Load(this ILayerFileIO dataIO, IZone zone, LayerType layerType) where T : struct { - return dataIO.LoadLayerData(zone,layerType.ToString()); + return dataIO.LoadLayerData(zone, layerType.ToString()); } } @@ -38,28 +38,30 @@ public LayerFileIO(IFileSystem fileSystem) _fileSystem = fileSystem; } - public T[] LoadLayerData(IZone zone,string name) where T : struct + public T[] LoadLayerData(IZone zone, string name) where T : struct { - var path = zone.CreateTerrainDataFilename(name); - var data = _fileSystem.ReadLayer(path); + string path = zone.CreateTerrainDataFilename(name); + T[] data = _fileSystem.ReadLayer(path); Logger.Info("Layer data loaded. (" + name + ") zone:" + zone.Id); return data; } public void SaveLayerToDisk(IZone zone, ILayer layer) where T : struct { - var baseFilename = zone.CreateTerrainDataFilename(layer.LayerType.ToString().ToLower(),""); + string baseFilename = zone.CreateTerrainDataFilename(layer.LayerType.ToString().ToLower(), ""); - using (var md5 = MD5.Create()) + using (MD5 md5 = MD5.Create()) { - var tmpFn = baseFilename + "tmp" + DateTime.Now.Ticks + ".bin"; - var layerData = layer.RawData.ToByteArray(); - _fileSystem.WriteLayer(tmpFn,layerData); + string tmpFn = baseFilename + "tmp" + DateTime.Now.Ticks + ".bin"; + byte[] layerData = layer.RawData.ToByteArray(); + _fileSystem.WriteLayer(tmpFn, layerData); if (!md5.ComputeHash(layerData).SequenceEqual(md5.ComputeHash(_fileSystem.ReadLayerAsByteArray(tmpFn)))) + { return; + } - _fileSystem.MoveLayerFile(tmpFn,baseFilename + "bin"); + _fileSystem.MoveLayerFile(tmpFn, baseFilename + "bin"); Logger.Info("Layer saved. (" + baseFilename + ")"); } } @@ -67,8 +69,9 @@ public void SaveLayerToDisk(IZone zone, ILayer layer) where T : struct public static partial class ZoneExtensions { - public static List FindWalkableArea(this IZone zone, Area area, int size,double slope = 4.0) + public static List FindWalkableArea(this IZone zone, Area area, int size, double slope = 4.0) { + area = area.Clamp(zone.Size); while (true) { Point startPosition; @@ -76,28 +79,31 @@ public static List FindWalkableArea(this IZone zone, Area area, int size, { startPosition = area.GetRandomPosition(); - if (!zone.Terrain.Blocks.GetValue(startPosition).Island && zone.IsWalkable(startPosition,slope)) + if (!zone.Terrain.Blocks.GetValue(startPosition).Island && zone.IsWalkable(startPosition, slope)) + { break; + } } - var p = FindWalkableArea(zone, startPosition, area, size,slope); + List p = FindWalkableArea(zone, startPosition, area, size, slope); if (p != null) + { return p; + } Thread.Sleep(1); } } [CanBeNull] - public static List FindWalkableArea(this IZone zone,Point startPosition, Area area, int size,double slope = 4.0) + public static List FindWalkableArea(this IZone zone, Point startPosition, Area area, int size, double slope = 4.0) { - var q = new Queue(); + Queue q = new Queue(); q.Enqueue(startPosition); - var closed = new HashSet {startPosition}; + HashSet closed = new HashSet { startPosition }; - var result = new List(); - Point position; - while (q.TryDequeue(out position)) + List result = new List(); + while (q.TryDequeue(out Point position)) { result.Add(position); @@ -107,15 +113,19 @@ public static List FindWalkableArea(this IZone zone,Point startPosition, return result; } - foreach (var np in position.GetNonDiagonalNeighbours()) + foreach (Point np in position.GetNonDiagonalNeighbours()) { if (closed.Contains(np)) + { continue; + } - closed.Add(np); + _ = closed.Add(np); - if (!area.Contains(np) || !zone.IsWalkable(np,slope)) + if (!area.Contains(np) || !zone.IsWalkable(np, slope)) + { continue; + } q.Enqueue(np); } @@ -124,21 +134,64 @@ public static List FindWalkableArea(this IZone zone,Point startPosition, return null; } + /// + /// A 2d raycast check for a line segment in cellular world + /// An implementation of Bresenham's line algorithm + /// + /// this + /// Start point of line segment + /// End point of line segment + /// Slope capability check for slope-based blocking + /// True if tiles checked are walkable + public static bool CheckLinearPath(this IZone zone, Point start, Point end, double slope = 4.0) + { + int x = start.X; + int y = start.Y; + int deltaX = Math.Abs(end.X - x); + int deltaY = Math.Abs(end.Y - y); + int travelDist = deltaX + deltaY; + int xIncrement = (end.X > x) ? 1 : -1; + int yIncrement = (end.Y > y) ? 1 : -1; + int error = deltaX - deltaY; + deltaX *= 2; + deltaY *= 2; + + for (int i = 0; i <= travelDist; i++) + { + if (!zone.IsWalkable(x, y, slope)) + { + return false; + } + + if (error > 0) + { + x += xIncrement; + error -= deltaY; + } + else + { + y += yIncrement; + error += deltaX; + } + } + return true; + } + public static bool IsTerrainConditionsMatchInRange(this IZone zone, Position centerPosition, int range, double slope) { - var totalTiles = range * range * 4; + int totalTiles = range * range * 4; - var illegalsFound = 0; + int illegalsFound = 0; - for (var j = centerPosition.intY - range; j < centerPosition.intY + range; j++) + for (int j = centerPosition.intY - range; j < centerPosition.intY + range; j++) { - for (var i = centerPosition.intX - range; i < centerPosition.intX + range; i++) + for (int i = centerPosition.intX - range; i < centerPosition.intX + range; i++) { - var cPos = new Position(i, j); + Position cPos = new Position(i, j); if (centerPosition.IsInRangeOf2D(cPos, range)) { - var blockInfo = zone.Terrain.Blocks.GetValue(i, j); + BlockingInfo blockInfo = zone.Terrain.Blocks.GetValue(i, j); if (blockInfo.Height > 0 || blockInfo.NonNaturally || blockInfo.Plant || !zone.Terrain.Slope.CheckSlope(cPos.intX, cPos.intY, slope)) { @@ -148,34 +201,38 @@ public static bool IsTerrainConditionsMatchInRange(this IZone zone, Position cen } } - var troubleFactor = illegalsFound / (double)totalTiles; + double troubleFactor = illegalsFound / (double)totalTiles; Logger.Info("trouble factor: " + troubleFactor); if (troubleFactor > 0.5) { - Logger.Warning("illegal tiles coverage: " + troubleFactor * 100 + "%"); + Logger.Warning("illegal tiles coverage: " + (troubleFactor * 100) + "%"); return false; } return true; } - public static void DoAoeDamageAsync(this IZone zone,IBuilder damageBuilder) + public static void DoAoeDamageAsync(this IZone zone, IBuilder damageBuilder) { - Task.Run(() => DoAoeDamage(zone, damageBuilder)); + _ = Task.Run(() => DoAoeDamage(zone, damageBuilder)); } - public static void DoAoeDamage(this IZone zone,IBuilder damageBuilder) + public static void DoAoeDamage(this IZone zone, IBuilder damageBuilder) { - var damageInfo = damageBuilder.Build(); - var units = zone.Units.WithinRange(damageInfo.sourcePosition, damageInfo.Range); + DamageInfo damageInfo = damageBuilder.Build(); + IEnumerable units = zone.Units + .WithinRange(damageInfo.sourcePosition, damageInfo.Range) + .Where(x => !x.HasTeleportSicknessEffect || x.HasPvpEffect); - foreach (var unit in units) + foreach (Unit unit in units) { - var losResult = zone.IsInLineOfSight(damageInfo.attacker, unit, false); + LOSResult losResult = zone.IsInLineOfSight(damageInfo.attacker, unit, false); if (losResult.hit) + { continue; + } unit.TakeDamage(damageInfo); } @@ -200,16 +257,20 @@ public static string CreateTerrainDataFilename(int zoneId, string name, string e public static Position FindPassablePointInRadius(this IZone zone, Position origin, int radius) { - var counter = 0; + int counter = 0; while (true) { counter++; if (counter > MAX_SAMPLES) - return default(Position); + { + return default; + } - var randomPos = origin.GetRandomPositionInRange2D(0, radius).Clamp(zone.Size); + Position randomPos = origin.GetRandomPositionInRange2D(0, radius).Clamp(zone.Size); if (zone.Terrain.IsPassable(randomPos)) + { return randomPos; + } } } @@ -218,16 +279,16 @@ public static bool IsValidPosition(this IZone zone, int x, int y) return x >= 0 && x < zone.Size.Width && y >= 0 && y < zone.Size.Height; } - public static void UpdateCorporation(this IZone zone,CorporationCommand command,Dictionary data) + public static void UpdateCorporation(this IZone zone, CorporationCommand command, Dictionary data) { - zone.CorporationHandler.HandleCorporationCommand(command,data); + zone.CorporationHandler.HandleCorporationCommand(command, data); } - public static Position ToWorldPosition(this IZone zone,Position position) + public static Position ToWorldPosition(this IZone zone, Position position) { - var zx = zone.Configuration.WorldPosition.X; - var zy = zone.Configuration.WorldPosition.Y; - return position.GetWorldPosition(zx,zy); + int zx = zone.Configuration.WorldPosition.X; + int zy = zone.Configuration.WorldPosition.Y; + return position.GetWorldPosition(zx, zy); } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index 302bcc247..132b88c33 100644 --- a/src/Perpetuum/Zones/ZoneSession.cs +++ b/src/Perpetuum/Zones/ZoneSession.cs @@ -24,6 +24,7 @@ using Perpetuum.Robots; using Perpetuum.Services.Looting; using Perpetuum.Services.Sessions; +using Perpetuum.Services.Weather; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Beams; @@ -234,18 +235,19 @@ private void WritePacketLog(Packet packet, string message = null) [Conditional("DEBUG")] private void LogGenxyException(Packet packet, PerpetuumException gex) { + var playerInfo = _player != null ? _player.InfoString : null; var e = new LogEvent { LogType = LogType.Error, Tag = "ZPACKET", - Message = $"command:{packet.Command} zone:{_zone.Id} player:{_player.InfoString} ex:{gex}" + Message = $"command:{packet.Command} zone:{_zone.Id} player:{playerInfo} ex:{gex}" }; Logger.Log(e); } private BeamsMonitor _beamsMonitor; - private Observer _weatherMonitor; + private WeatherMonitor _weatherMonitor; private void HandleAuth(Packet packet) { @@ -255,6 +257,14 @@ private void HandleAuth(Packet packet) var character = ZoneTicket.GetCharacterFromEncryptedTicket(encrypted); character.ThrowIfEqual(null, ErrorCodes.WTFErrorMedicalAttentionSuggested); + + var characterSession = _sessionManager.GetByCharacter(character); + + if (characterSession == null || !characterSession.IsAuthenticated || + !characterSession.RemoteEndPoint.Address.Equals(_connection.RemoteEndPoint.Address)) { + throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } + Logger.Info($"Socket authentication successful. zone: {_zone.Id} character: {character.Id}"); Character = character; AccessLevel = character.AccessLevel; @@ -271,21 +281,22 @@ private void HandleAuth(Packet packet) _beamsMonitor = new BeamsMonitor(this); _beamsMonitor.Subscribe(_zone.Beams); - _weatherMonitor = Observer.Create(OnWeatherUpdated); - _zone.Weather.Subscribe(_weatherMonitor); + _weatherMonitor = new WeatherMonitor(OnWeatherUpdated); + _weatherMonitor.Subscribe(_zone.Weather); _terrainUpdateNotifier = CreateTerrainNotifier(player); player.SetSession(this); player.SendInitSelf(); player.ApplyTeleportSicknessEffect(); + player.ApplyInvulnerableEffect(); _player = player; } - private void OnWeatherUpdated(Packet weatherUpdatePacket) + private void OnWeatherUpdated(WeatherInfo weather) { - SendPacket(weatherUpdatePacket); + SendPacket(weather.CreateUpdatePacket()); } private TerrainUpdateNotifier CreateTerrainNotifier(Player player) @@ -307,10 +318,9 @@ private void HandleClientUpdate(Packet packet) var speed = (float)packet.ReadByte() / 255; var direction = (float)packet.ReadByte() / 255; - if ( !player.IsWalkable(position) ) + if (!player.TryMove(position)) throw new PerpetuumException(ErrorCodes.InvalidMovement); - player.CurrentPosition = position; player.CurrentSpeed = speed; player.Direction = direction; } @@ -685,6 +695,7 @@ private void HandleRemoveLock(Packet packet) private void HandleSetLayer(Packet packet) { _player.Session.AccessLevel.IsAdminOrGm().ThrowIfFalse(ErrorCodes.AccessDenied); + _zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); using (new TerrainUpdateMonitor(_zone)) { diff --git a/src/Perpetuum/Zones/ZoneType.cs b/src/Perpetuum/Zones/ZoneType.cs index 2f732a33a..c477dcbbc 100644 --- a/src/Perpetuum/Zones/ZoneType.cs +++ b/src/Perpetuum/Zones/ZoneType.cs @@ -5,6 +5,7 @@ public enum ZoneType Undefined = 0, Pve, Pvp, - Training + Training, + Stronghold } } \ No newline at end of file diff --git a/src/Perpetuum/packages.config b/src/Perpetuum/packages.config index 643299cd1..5f43db920 100644 --- a/src/Perpetuum/packages.config +++ b/src/Perpetuum/packages.config @@ -1,5 +1,6 @@  +