From d0e4627176b8017079bdcfe9c07d9f7e79828578 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 10 Jan 2018 20:52:55 -0500 Subject: [PATCH 001/457] Gitignore and startup args (#1) Gitignore and args --- .gitignore | 261 ++++++++++++++++++ .../Perpetuum.Server.csproj.user | 12 +- 2 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3c4efe206 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## 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 +artifacts/ + +*_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 \ 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 From 60745d4973e4e3cbf39bdc7ed5e1fa5db6c1f739 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Wed, 10 Jan 2018 21:19:14 -0500 Subject: [PATCH 002/457] Fix for field terminals. --- src/Perpetuum.RequestHandlers/GetRobotInfo.cs | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) 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(); + } } } From ce1cce08fc1bd86b3e99b62ea0f603ad6aaf3400 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Thu, 11 Jan 2018 00:02:55 -0500 Subject: [PATCH 003/457] fix remote market server error --- .../Services/MarketEngine/MarketOrderRepository.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs index 6d78486ca..f70962737 100644 --- a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs +++ b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs @@ -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; @@ -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) From 28c72664db0589f5aa33b5a193fdfe3b9e9e8b0a Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Thu, 11 Jan 2018 09:08:01 -0500 Subject: [PATCH 004/457] gives player equal syndicate protection and molecular instability when teleporting. --- src/Perpetuum/Players/Player.cs | 6 ++++++ .../Zones/Teleporting/Strategies/TeleportWithinZone.cs | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 979896ad2..7a80d7bbb 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -318,6 +318,12 @@ public void ApplyInvulnerableEffect() ApplyEffect(builder); } + public void RemoveInvulnerableEffect() + { + EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); + } + + public void ApplyTeleportSicknessEffect() { var zone = Zone; diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs index 3aa88de06..087c79cff 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs @@ -53,8 +53,15 @@ public Task DoTeleportAsync(Player player) player.AddToZone(zone,validPosition,ZoneEnterType.LocalTeleport); player.SendInitSelf(); - if ( ApplyTeleportSickness ) + if (ApplyTeleportSickness) + { player.ApplyTeleportSicknessEffect(); + } + if (ApplyInvulnerable) + { + player.RemoveInvulnerableEffect(); // remove previous effect. + player.ApplyInvulnerableEffect(); // re-add effect to restart counter. + } // mozoghat, fade out player.States.InMoveable = false; From ea04e4c1411bce7fb6715d187276eb811dc2c52a Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 11 Jan 2018 20:15:15 -0500 Subject: [PATCH 005/457] Tweak syndicate protection to be 25% less than instability --- src/Perpetuum/Players/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 7a80d7bbb..68472e8e5 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -315,6 +315,7 @@ public void SendModuleProcessError(Module module, ErrorCodes error) public void ApplyInvulnerableEffect() { var builder = NewEffectBuilder().SetType(EffectType.effect_invulnerable); + builder.WithDurationModifier(0.75); //Reduce span of syndicate protection ApplyEffect(builder); } From 3f8849c07949bd90e9e8e8d822426feabab76386 Mon Sep 17 00:00:00 2001 From: LoyalServant Date: Thu, 11 Jan 2018 20:26:03 -0500 Subject: [PATCH 006/457] Fix for field terminals. (#2) * Fix for field terminals. * fix remote market server error * gives player equal syndicate protection and molecular instability when teleporting. * Tweak syndicate protection to be 25% less than instability --- src/Perpetuum.RequestHandlers/GetRobotInfo.cs | 74 ++++++++++--------- src/Perpetuum/Players/Player.cs | 7 ++ .../MarketEngine/MarketOrderRepository.cs | 6 +- .../Strategies/TeleportWithinZone.cs | 9 ++- 4 files changed, 57 insertions(+), 39 deletions(-) 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/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 979896ad2..68472e8e5 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -315,9 +315,16 @@ public void SendModuleProcessError(Module module, ErrorCodes error) public void ApplyInvulnerableEffect() { var 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; diff --git a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs index 6d78486ca..f70962737 100644 --- a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs +++ b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs @@ -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; @@ -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/Zones/Teleporting/Strategies/TeleportWithinZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs index 3aa88de06..087c79cff 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs @@ -53,8 +53,15 @@ public Task DoTeleportAsync(Player player) player.AddToZone(zone,validPosition,ZoneEnterType.LocalTeleport); player.SendInitSelf(); - if ( ApplyTeleportSickness ) + if (ApplyTeleportSickness) + { player.ApplyTeleportSicknessEffect(); + } + if (ApplyInvulnerable) + { + player.RemoveInvulnerableEffect(); // remove previous effect. + player.ApplyInvulnerableEffect(); // re-add effect to restart counter. + } // mozoghat, fade out player.States.InMoveable = false; From 75cc3bce20b28399faabb3ac37bd4e83333933e9 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 13 Jan 2018 09:17:13 -0500 Subject: [PATCH 007/457] test feature: alter rift system to give us a random jump to the stronghold zone. --- .../Accounting/Characters/Character.cs | 5 +++ src/Perpetuum/Services/RiftSystem/Rift.cs | 33 ++++++++++++++----- .../Services/RiftSystem/RiftManager.cs | 19 +++++++++-- .../Strategies/TeleportToAnotherZone.cs | 24 ++++++++++++++ 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum/Accounting/Characters/Character.cs b/src/Perpetuum/Accounting/Characters/Character.cs index cc71d65ee..f9201fd7b 100644 --- a/src/Perpetuum/Accounting/Characters/Character.cs +++ b/src/Perpetuum/Accounting/Characters/Character.cs @@ -1055,6 +1055,11 @@ 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; diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index ff9e047f5..e0c710fae 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -131,6 +131,8 @@ public Rift(ITeleportStrategyFactories teleportStrategyFactories) _blobEmitter = new BlobEmitter(this); } + public bool IsDestinationStronghold { get; set; } = false; + public void SetDespawnTime(TimeSpan despawnTime) { _despawnHelper = UnitDespawnHelper.Create(this,despawnTime); @@ -175,17 +177,30 @@ public void UseItem(Player player) { player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); - var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); - if (nearestRift == null) - throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + IZone destination = player.Character.GetZone(16); - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); - teleport.TargetPosition = nearestRift.CurrentPosition; - teleport.ApplyTeleportSickness = true; - teleport.ApplyInvulnerable = true; - teleport.DoTeleportAsync(player); + // teleport player to stronghold + // stronghold zone must be active. + if (this.IsDestinationStronghold && destination != null) + { + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destination); + teleport.DoTeleportAsync(player); + } + else + { + player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); + + var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); + if (nearestRift == null) + throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + + var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); + teleport.TargetPosition = nearestRift.CurrentPosition; + teleport.ApplyTeleportSickness = true; + teleport.ApplyInvulnerable = true; + teleport.DoTeleportAsync(player); + } } public double BlobEmission => _blobEmitter.BlobEmission; diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 4662b4aac..40c2c7a87 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -61,6 +61,8 @@ public class RiftManager private readonly TimeRange _spawnTime; private readonly RiftSpawnPositionFinder _spawnPositionFinder; private readonly IEntityServices _entityServices; + private bool StrongHoldRiftGenerated { get; set; } = false; + private Random r; private readonly LinkedList _nextRiftSpawns = new LinkedList(); @@ -70,18 +72,21 @@ public RiftManager(IZone zone,TimeRange spawnTime,RiftSpawnPositionFinder spawnP _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; + r = new Random(); } private int _riftCounts; public void Update(TimeSpan time) { - while (_riftCounts < 20) + // FIXME: this needs to be in the database. + // do not spawn rifts on zone 16 (the stronghold) + while (_riftCounts < 40 && _zone.Id != 16) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); } - + _nextRiftSpawns.RemoveAll(t => { t.Update(time); @@ -100,9 +105,17 @@ private void SpawnRift() rift.SetDespawnTime(TimeSpan.FromHours(3)); rift.RemovedFromZone += OnRiftRemovedFromZone; + // only generate one stronghold teleport and make it random chance. + int rand = r.Next(0, 3); + if (rand == 2 && !StrongHoldRiftGenerated) + { + rift.IsDestinationStronghold = true; + this.StrongHoldRiftGenerated = true; + } + var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); - Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ")"); + Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ") Stronghold:" + rift.IsDestinationStronghold); } private void OnRiftRemovedFromZone(Unit unit) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs index c65d76376..9df3b255e 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,28 @@ public void DoTeleport(Player player) Task.Delay(1000).ContinueWith(t => _targetZone.Enter(character,Commands.TeleportUse)); }); } + + [CanBeNull] + public Task DoTeleportAsync(Player player) + { + // FIXME: this needs to calculate a proper position but the server does not have the altitude data for this map + var validPosition = new Position(1015, 1075); + 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 From 66042c7f2fefea71673dd3481cac69194d28c931 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 13 Jan 2018 22:05:29 -0500 Subject: [PATCH 008/457] modify rifts slightly. --- src/Perpetuum/Services/RiftSystem/RiftManager.cs | 5 ++--- .../Zones/Teleporting/Strategies/TeleportToAnotherZone.cs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 40c2c7a87..489f3ef73 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -81,7 +81,7 @@ public void Update(TimeSpan time) { // FIXME: this needs to be in the database. // do not spawn rifts on zone 16 (the stronghold) - while (_riftCounts < 40 && _zone.Id != 16) + while (_riftCounts < 10 && _zone.Id != 16) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); @@ -106,11 +106,10 @@ private void SpawnRift() rift.RemovedFromZone += OnRiftRemovedFromZone; // only generate one stronghold teleport and make it random chance. - int rand = r.Next(0, 3); + int rand = r.Next(0, 10); if (rand == 2 && !StrongHoldRiftGenerated) { rift.IsDestinationStronghold = true; - this.StrongHoldRiftGenerated = true; } var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs index 9df3b255e..70481ad1b 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs @@ -43,7 +43,7 @@ public void DoTeleport(Player player) public Task DoTeleportAsync(Player player) { // FIXME: this needs to calculate a proper position but the server does not have the altitude data for this map - var validPosition = new Position(1015, 1075); + var validPosition = new Position(1120, 1039); player.States.Teleport = true; Entity.Repository.ForceUpdate(player); var character = player.Character; From 8e852515ef02b9029ffa89ee19d8528e49ba1b4d Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sun, 14 Jan 2018 10:39:26 -0500 Subject: [PATCH 009/457] Fix player trades --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index e93e9d1a7..137cd9ab7 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1575,7 +1575,7 @@ private void InitRelayManager() _builder.RegisterType().As().SingleInstance(); _builder.RegisterType(); - _builder.RegisterType().As(); + _builder.RegisterType().SingleInstance().As(); _builder.RegisterType(); From 30ffe56fa8100b1ba09c54f63dc09e864ad1bb28 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 20 Jan 2018 19:17:36 -0500 Subject: [PATCH 010/457] Give new accounts EP when created. --- .gitattributes | 63 +++++++++++++++++++ .../AdminTools/AccountCreate.cs | 6 ++ .../AdminTools/AccountOpenCreate.cs | 6 ++ 3 files changed, 75 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs index ff1ab7d2e..1a73f75b6 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs @@ -1,4 +1,5 @@ using Perpetuum.Accounting; +using Perpetuum.Data; using Perpetuum.Host.Requests; namespace Perpetuum.RequestHandlers.AdminTools @@ -33,6 +34,11 @@ public void HandleRequest(IRequest request) _accountRepository.Insert(account); + Db.Query().CommandText("extensionPointsInject") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", 40000) + .ExecuteNonQuery(); + Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs index 1f25bf9e9..3704166ef 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs @@ -1,4 +1,5 @@ using Perpetuum.Accounting; +using Perpetuum.Data; using Perpetuum.Host.Requests; using Perpetuum.Services.Relay; @@ -41,6 +42,11 @@ public void HandleRequest(IRequest request) _accountRepository.Insert(account); + Db.Query().CommandText("extensionPointsInject") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", 40000) + .ExecuteNonQuery(); + Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } From d6077acd0e34a4d6a1bd2be4fe3a7418df5aede3 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 21 Jan 2018 15:55:50 -0500 Subject: [PATCH 011/457] ep activity falloff, starting, and target level (#6) --- src/Perpetuum/Accounting/AccountManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 77c22aff0..b27817c1f 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -100,9 +100,9 @@ public int GetLockedEpByAccount(Account account) return lockedEp; } - private const double BOOSTMULTIPLIERMAX = 50; - private const double BOOSTEXPONENT = 2.0; - private const double SERVER_DESIRED_EP_LEVEL = 3000000; + private const double BOOSTMULTIPLIERMAX = 25; + private const double BOOSTEXPONENT = 1.5; + private const double SERVER_DESIRED_EP_LEVEL = 500000; public IDictionary GetEPData(Account account,Character character) { From 537df8bdda46108a59f276729be836f11e57cb36 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 21 Jan 2018 17:05:19 -0500 Subject: [PATCH 012/457] Update LICENSE (#8) --- LICENSE | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/LICENSE b/LICENSE index f3cfd2a7c..99ece67c9 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 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 substaintial portions +of the Software will include attributions to the OpenPerpetuum 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. From c0ead676ee259a9b40bc254af7b1e502683480a3 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 21 Jan 2018 18:33:10 -0500 Subject: [PATCH 013/457] Syndicatification of assignments done; tokens and rep (#5) --- .../MissionStructures/MissionLocation.cs | 6 ++++++ .../MissionEngine/Missions/MissionVisitor.cs | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) 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/Missions/MissionVisitor.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs index f55ebe287..32a518cfe 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs @@ -162,8 +162,23 @@ public override void VisitRandomMission(RandomMission randomMission) var positiveAllianceEid = _missionLocation.Agent.OwnerAlliance.Eid; var opposingAllianceEid = DefaultCorporationDataCache.SelectOpposingAlliance(positiveAllianceEid); - _standingChanges = new[] {new MissionStandingChange(positiveAllianceEid, standingValue), new MissionStandingChange(opposingAllianceEid, negativeValue) }; - + + //TODO: Fixme: I am a hack for Syndicatification + if (this._missionLocation.zoneId == 0 || this._missionLocation.zoneId == 8) //For New Virginia and Hershfield + { + //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(); + }//--end hack-- + else + { + _standingChanges = new[] { new MissionStandingChange(positiveAllianceEid, standingValue), new MissionStandingChange(opposingAllianceEid, negativeValue) }; + } } } From d481551ee7bb235d3d7fc9b14954e0431d59a8dd Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 21 Jan 2018 18:33:27 -0500 Subject: [PATCH 014/457] Starting nic to 500k; TODO still should move to DB (#4) --- .../Zones/Teleporting/Strategies/TrainingExitStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index e82e5d0c2..66fad4851 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -14,7 +14,7 @@ namespace Perpetuum.Zones.Teleporting.Strategies { public class TrainingExitStrategy : ITeleportStrategy { - private const double CHARACTER_START_CREDIT = 20000; + private const double CHARACTER_START_CREDIT = 500000; //TODO: move to DB private const int MAX_REWARD_LEVEL = 4; private readonly TeleportDescription _description; From 505ef9d2d9f84cd35544906e10f581e9001bd566 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 27 Jan 2018 12:49:04 -0500 Subject: [PATCH 015/457] use unicoin on itemshop (#10) --- src/Perpetuum/Services/ItemShop/ItemShop.cs | 35 +++++++++++-------- .../Services/ItemShop/ItemShopEntry.cs | 23 ++++++++---- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/Perpetuum/Services/ItemShop/ItemShop.cs b/src/Perpetuum/Services/ItemShop/ItemShop.cs index 04a4825c1..a8d0197ab 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; @@ -42,7 +42,7 @@ private ItemShopEntry GetEntry(int entryID) JOIN itemshoplocations sl ON its.presetid = sl.presetid WHERE 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; @@ -56,7 +56,7 @@ private List GetAll() JOIN itemshoplocations sl ON its.presetid = sl.presetid WHERE 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 +82,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 +94,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 +112,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 +133,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 +156,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 From 54100d2a443d592e9d27d7928d86aa21bc52c5e3 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 28 Jan 2018 17:29:00 -0500 Subject: [PATCH 016/457] Pre-Alpha Server v1 (#12) * Gitignore and startup args (#1) Gitignore and args * Fix for field terminals. * fix remote market server error * gives player equal syndicate protection and molecular instability when teleporting. * Tweak syndicate protection to be 25% less than instability * Fix for field terminals. (#2) * Fix for field terminals. * fix remote market server error * gives player equal syndicate protection and molecular instability when teleporting. * Tweak syndicate protection to be 25% less than instability * test feature: alter rift system to give us a random jump to the stronghold zone. * modify rifts slightly. * Fix player trades * Give new accounts EP when created. * ep activity falloff, starting, and target level (#6) * Update LICENSE (#8) * Syndicatification of assignments done; tokens and rep (#5) * Starting nic to 500k; TODO still should move to DB (#4) * use unicoin on itemshop (#10) --- .gitattributes | 63 +++++ .gitignore | 261 ++++++++++++++++++ LICENSE | 31 +++ .../PerpetuumBootstrapper.cs | 2 +- .../AdminTools/AccountCreate.cs | 6 + .../AdminTools/AccountOpenCreate.cs | 6 + src/Perpetuum.RequestHandlers/GetRobotInfo.cs | 74 ++--- .../Perpetuum.Server.csproj.user | 12 +- src/Perpetuum/Accounting/AccountManager.cs | 6 +- .../Accounting/Characters/Character.cs | 5 + src/Perpetuum/Players/Player.cs | 7 + src/Perpetuum/Services/ItemShop/ItemShop.cs | 35 ++- .../Services/ItemShop/ItemShopEntry.cs | 23 +- .../MarketEngine/MarketOrderRepository.cs | 6 +- .../MissionStructures/MissionLocation.cs | 6 + .../MissionEngine/Missions/MissionVisitor.cs | 19 +- src/Perpetuum/Services/RiftSystem/Rift.cs | 33 ++- .../Services/RiftSystem/RiftManager.cs | 18 +- .../Strategies/TeleportToAnotherZone.cs | 24 ++ .../Strategies/TeleportWithinZone.cs | 9 +- .../Strategies/TrainingExitStrategy.cs | 2 +- 21 files changed, 568 insertions(+), 80 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3c4efe206 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## 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 +artifacts/ + +*_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 \ No newline at end of file diff --git a/LICENSE b/LICENSE index f3cfd2a7c..99ece67c9 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 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 substaintial portions +of the Software will include attributions to the OpenPerpetuum 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/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index e93e9d1a7..137cd9ab7 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1575,7 +1575,7 @@ private void InitRelayManager() _builder.RegisterType().As().SingleInstance(); _builder.RegisterType(); - _builder.RegisterType().As(); + _builder.RegisterType().SingleInstance().As(); _builder.RegisterType(); diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs index ff1ab7d2e..1a73f75b6 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs @@ -1,4 +1,5 @@ using Perpetuum.Accounting; +using Perpetuum.Data; using Perpetuum.Host.Requests; namespace Perpetuum.RequestHandlers.AdminTools @@ -33,6 +34,11 @@ public void HandleRequest(IRequest request) _accountRepository.Insert(account); + Db.Query().CommandText("extensionPointsInject") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", 40000) + .ExecuteNonQuery(); + Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs index 1f25bf9e9..3704166ef 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs @@ -1,4 +1,5 @@ using Perpetuum.Accounting; +using Perpetuum.Data; using Perpetuum.Host.Requests; using Perpetuum.Services.Relay; @@ -41,6 +42,11 @@ public void HandleRequest(IRequest request) _accountRepository.Insert(account); + Db.Query().CommandText("extensionPointsInject") + .SetParameter("@accountID", account.Id) + .SetParameter("@points", 40000) + .ExecuteNonQuery(); + Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } 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.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/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 77c22aff0..b27817c1f 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -100,9 +100,9 @@ public int GetLockedEpByAccount(Account account) return lockedEp; } - private const double BOOSTMULTIPLIERMAX = 50; - private const double BOOSTEXPONENT = 2.0; - private const double SERVER_DESIRED_EP_LEVEL = 3000000; + private const double BOOSTMULTIPLIERMAX = 25; + private const double BOOSTEXPONENT = 1.5; + private const double SERVER_DESIRED_EP_LEVEL = 500000; public IDictionary GetEPData(Account account,Character character) { diff --git a/src/Perpetuum/Accounting/Characters/Character.cs b/src/Perpetuum/Accounting/Characters/Character.cs index cc71d65ee..f9201fd7b 100644 --- a/src/Perpetuum/Accounting/Characters/Character.cs +++ b/src/Perpetuum/Accounting/Characters/Character.cs @@ -1055,6 +1055,11 @@ 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; diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 979896ad2..68472e8e5 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -315,9 +315,16 @@ public void SendModuleProcessError(Module module, ErrorCodes error) public void ApplyInvulnerableEffect() { var 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; diff --git a/src/Perpetuum/Services/ItemShop/ItemShop.cs b/src/Perpetuum/Services/ItemShop/ItemShop.cs index 04a4825c1..a8d0197ab 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; @@ -42,7 +42,7 @@ private ItemShopEntry GetEntry(int entryID) JOIN itemshoplocations sl ON its.presetid = sl.presetid WHERE 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; @@ -56,7 +56,7 @@ private List GetAll() JOIN itemshoplocations sl ON its.presetid = sl.presetid WHERE 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 +82,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 +94,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 +112,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 +133,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 +156,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/MarketEngine/MarketOrderRepository.cs b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs index 6d78486ca..f70962737 100644 --- a/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs +++ b/src/Perpetuum/Services/MarketEngine/MarketOrderRepository.cs @@ -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; @@ -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/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/Missions/MissionVisitor.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs index f55ebe287..32a518cfe 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs @@ -162,8 +162,23 @@ public override void VisitRandomMission(RandomMission randomMission) var positiveAllianceEid = _missionLocation.Agent.OwnerAlliance.Eid; var opposingAllianceEid = DefaultCorporationDataCache.SelectOpposingAlliance(positiveAllianceEid); - _standingChanges = new[] {new MissionStandingChange(positiveAllianceEid, standingValue), new MissionStandingChange(opposingAllianceEid, negativeValue) }; - + + //TODO: Fixme: I am a hack for Syndicatification + if (this._missionLocation.zoneId == 0 || this._missionLocation.zoneId == 8) //For New Virginia and Hershfield + { + //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(); + }//--end hack-- + else + { + _standingChanges = new[] { new MissionStandingChange(positiveAllianceEid, standingValue), new MissionStandingChange(opposingAllianceEid, negativeValue) }; + } } } diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index ff9e047f5..e0c710fae 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -131,6 +131,8 @@ public Rift(ITeleportStrategyFactories teleportStrategyFactories) _blobEmitter = new BlobEmitter(this); } + public bool IsDestinationStronghold { get; set; } = false; + public void SetDespawnTime(TimeSpan despawnTime) { _despawnHelper = UnitDespawnHelper.Create(this,despawnTime); @@ -175,17 +177,30 @@ public void UseItem(Player player) { player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); - var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); - if (nearestRift == null) - throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + IZone destination = player.Character.GetZone(16); - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); - teleport.TargetPosition = nearestRift.CurrentPosition; - teleport.ApplyTeleportSickness = true; - teleport.ApplyInvulnerable = true; - teleport.DoTeleportAsync(player); + // teleport player to stronghold + // stronghold zone must be active. + if (this.IsDestinationStronghold && destination != null) + { + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destination); + teleport.DoTeleportAsync(player); + } + else + { + player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); + + var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); + if (nearestRift == null) + throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + + var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); + teleport.TargetPosition = nearestRift.CurrentPosition; + teleport.ApplyTeleportSickness = true; + teleport.ApplyInvulnerable = true; + teleport.DoTeleportAsync(player); + } } public double BlobEmission => _blobEmitter.BlobEmission; diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 4662b4aac..489f3ef73 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -61,6 +61,8 @@ public class RiftManager private readonly TimeRange _spawnTime; private readonly RiftSpawnPositionFinder _spawnPositionFinder; private readonly IEntityServices _entityServices; + private bool StrongHoldRiftGenerated { get; set; } = false; + private Random r; private readonly LinkedList _nextRiftSpawns = new LinkedList(); @@ -70,18 +72,21 @@ public RiftManager(IZone zone,TimeRange spawnTime,RiftSpawnPositionFinder spawnP _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; + r = new Random(); } private int _riftCounts; public void Update(TimeSpan time) { - while (_riftCounts < 20) + // FIXME: this needs to be in the database. + // do not spawn rifts on zone 16 (the stronghold) + while (_riftCounts < 10 && _zone.Id != 16) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); } - + _nextRiftSpawns.RemoveAll(t => { t.Update(time); @@ -100,9 +105,16 @@ private void SpawnRift() rift.SetDespawnTime(TimeSpan.FromHours(3)); rift.RemovedFromZone += OnRiftRemovedFromZone; + // only generate one stronghold teleport and make it random chance. + int rand = r.Next(0, 10); + if (rand == 2 && !StrongHoldRiftGenerated) + { + rift.IsDestinationStronghold = true; + } + var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); - Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ")"); + Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ") Stronghold:" + rift.IsDestinationStronghold); } private void OnRiftRemovedFromZone(Unit unit) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs index c65d76376..70481ad1b 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,28 @@ public void DoTeleport(Player player) Task.Delay(1000).ContinueWith(t => _targetZone.Enter(character,Commands.TeleportUse)); }); } + + [CanBeNull] + public Task DoTeleportAsync(Player player) + { + // FIXME: this needs to calculate a proper position but the server does not have the altitude data for this map + var validPosition = new Position(1120, 1039); + 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..087c79cff 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs @@ -53,8 +53,15 @@ public Task DoTeleportAsync(Player player) player.AddToZone(zone,validPosition,ZoneEnterType.LocalTeleport); player.SendInitSelf(); - if ( ApplyTeleportSickness ) + if (ApplyTeleportSickness) + { player.ApplyTeleportSicknessEffect(); + } + if (ApplyInvulnerable) + { + player.RemoveInvulnerableEffect(); // remove previous effect. + player.ApplyInvulnerableEffect(); // re-add effect to restart counter. + } // mozoghat, fade out player.States.InMoveable = false; diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index e82e5d0c2..66fad4851 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -14,7 +14,7 @@ namespace Perpetuum.Zones.Teleporting.Strategies { public class TrainingExitStrategy : ITeleportStrategy { - private const double CHARACTER_START_CREDIT = 20000; + private const double CHARACTER_START_CREDIT = 500000; //TODO: move to DB private const int MAX_REWARD_LEVEL = 4; private readonly TeleportDescription _description; From 18b35b7a35d7bed54ba496ccb96cb7041d4407a8 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sun, 28 Jan 2018 19:50:00 -0500 Subject: [PATCH 017/457] Fix for #52 where production missions would not advance. (#13) --- src/Perpetuum/EntityFramework/Entity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/EntityFramework/Entity.cs b/src/Perpetuum/EntityFramework/Entity.cs index d2cabadb5..0012ba81a 100644 --- a/src/Perpetuum/EntityFramework/Entity.cs +++ b/src/Perpetuum/EntityFramework/Entity.cs @@ -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); } From e4f2c81cd37fd1ecfbd0f3f157fbeab94baeb443 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 29 Jan 2018 18:33:35 -0500 Subject: [PATCH 018/457] add User-Agent to avoid being 403'd by current web server (#14) --- src/Perpetuum/Network/Http.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 Date: Mon, 29 Jan 2018 18:59:08 -0500 Subject: [PATCH 019/457] in-game command interface (#16) * rudimentary in-game command interface * more commands added. * more layer editing abilities. * bugged it! --- .../Channels/ChannelTalk.cs | 2 +- src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Services/Channels/Channel.cs | 3 +- .../Services/Channels/ChannelManager.cs | 11 +- .../Services/Channels/ChannelType.cs | 3 +- .../Services/Channels/GameAdminCommands.cs | 493 ++++++++++++++++++ .../Services/Channels/IChannelManager.cs | 3 +- src/Perpetuum/Services/Sessions/Session.cs | 22 + src/Perpetuum/Zones/Locking/LockHandler.cs | 5 +- 9 files changed, 535 insertions(+), 8 deletions(-) create mode 100644 src/Perpetuum/Services/Channels/GameAdminCommands.cs 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index b5178502b..957942098 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -276,6 +276,7 @@ + diff --git a/src/Perpetuum/Services/Channels/Channel.cs b/src/Perpetuum/Services/Channels/Channel.cs index 64269c297..9384e08f6 100644 --- a/src/Perpetuum/Services/Channels/Channel.cs +++ b/src/Perpetuum/Services/Channels/Channel.cs @@ -13,6 +13,7 @@ public class Channel public string Name { get; private set; } public string Topic { get; private set; } public string Password { get; private set; } + public GameAdminCommands AdminCommands = new GameAdminCommands(); public IChannelLogger Logger { get; private set; } @@ -198,7 +199,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..45c6cc3b7 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -5,6 +5,7 @@ using System.Threading; using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers; +using Perpetuum.Host.Requests; using Perpetuum.Services.Sessions; namespace Perpetuum.Services.Channels @@ -249,7 +250,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 +263,13 @@ public void Talk(string channelName, Character sender, string message) channel.Logger.LogMessage(sender, message); m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); - channel.SendMessageToAll(_sessionManager,sender,message); + channel.SendMessageToAll(_sessionManager, sender, message); + // this is an admin or GM command sent on an admin channel. + if (message.Substring(0,1) == "#" && channel.Type == ChannelType.Admin) + { + channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager); + } + } public void KickOrBan(string channelName, Character issuer, Character character, string message, bool ban) 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/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs new file mode 100644 index 000000000..cf3f5e054 --- /dev/null +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -0,0 +1,493 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.GenXY; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Sessions; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Services.Channels +{ + public class GameAdminCommands + { + public GameAdminCommands() + { + + } + + // obviously everything coming in from the in-game chat is a string. + // we have to take that string and chop it up, work out what command is being executed + // then parse/cast/convert arguments as necessary. + public void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, ISessionManager sessionManager) + { + string[] command = text.Split(new char[] { ',' }); + + if (command[0] == "#shutdown") + { + DateTime shutdownin = DateTime.Now; + int minutes = 1; + if (!int.TryParse(command[2], out minutes)) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + shutdownin = shutdownin.AddMinutes(minutes); + + Dictionary dictionary = new Dictionary() + { + { "message", command[1] }, + { "date", shutdownin } + }; + + string cmd = string.Format("serverShutDown:relay:{0}", GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#shutdowncancel") + { + string cmd = string.Format("serverShutDownCancel:relay:null"); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#jumpto") + { + bool err = false; + err = !int.TryParse(command[1], out int zone); + err = !int.TryParse(command[2], out int x); + err = !int.TryParse(command[3], out int y); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "zoneID" , zone }, + { "x" , x }, + { "y" , y } + }; + + string cmd = string.Format("jumpAnywhere:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#moveplayer") + { + bool err = false; + err = !int.TryParse(command[1], out int characterID); + err = !int.TryParse(command[2], out int x); + err = !int.TryParse(command[3], out int y); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "characterID" , characterID }, + { "x" , x }, + { "y" , y } + }; + + string cmd = string.Format("zoneMoveUnit:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzonecleanobstacleblocking") + { + string cmd = string.Format("zoneCleanObstacleBlocking:zone_{0}:null", sender.ZoneId); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzonedrawblockingbyeid") + { + bool err = false; + err = !Int64.TryParse(command[1], out Int64 eid); + + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "eid", eid } + }; + + string cmd = string.Format("zoneDrawBlockingByEid:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzoneremoveobjectbyeid") + { + bool err = false; + err = !Int64.TryParse(command[1], out Int64 eid); + + Dictionary dictionary = new Dictionary() + { + { "target", eid } + }; + + string cmd = string.Format("zoneRemoveObject:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#zonecreateisland") + { + bool err = false; + err = !int.TryParse(command[1], out int lvl); + + Dictionary dictionary = new Dictionary() + { + { "low", lvl } + }; + + string cmd = string.Format("zoneCreateIsland:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzoneplacewall") + { + string cmd = string.Format("zonePlaceWall:zone_{0}:null", sender.ZoneId); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzoneclearwalls") + { + string cmd = string.Format("zoneClearWalls:zone_{0}:null", sender.ZoneId); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzoneadddecor") + { + bool err = false; + err = !int.TryParse(command[1], out int definition); + err = !int.TryParse(command[2], out int x); + err = !int.TryParse(command[3], out int y); + err = !int.TryParse(command[4], out int z); + err = !double.TryParse(command[5], out double qx); + err = !double.TryParse(command[6], out double qy); + err = !double.TryParse(command[7], out double qz); + err = !double.TryParse(command[8], out double qw); + err = !double.TryParse(command[9], out double scale); + err = !int.TryParse(command[10], out int cat); + + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "x", x*255 }, + { "y", y*255 }, + { "z", z }, + { "quaternionX", qx }, + { "quaternionY", qy }, + { "quaternionZ", qz }, + { "quaternionW", qw }, + { "scale", scale }, + { "category", cat } + }; + + string cmd = string.Format("zoneDecorAdd:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#adddecortolockedtile") + { + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var terrainLock = player.GetPrimaryLock() as TerrainLock; + if (terrainLock == null) + { + return; + } + + int x = terrainLock.Location.intX; + int y = terrainLock.Location.intY; + int z = terrainLock.Location.intZ; + + bool err = !double.TryParse(command[2], out double scale); + err = !int.TryParse(command[1], out int definition); + + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "x", x*255 }, + { "y", y*255 }, + { "z", z*255 }, + { "quaternionX", (double)0 }, + { "quaternionY", (double)0 }, + { "quaternionZ", (double)0 }, + { "quaternionW", (double)0 }, + { "scale", scale }, + { "category", 1 } + }; + + string cmd = string.Format("zoneDecorAdd:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#zonedeletedecor") + { + bool err = false; + err = !int.TryParse(command[1], out int idno); + + Dictionary dictionary = new Dictionary() + { + { "ID", idno } + }; + + string cmd = string.Format("zoneDecorDelete:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#zoneclearlayer") + { + + Dictionary dictionary = new Dictionary() + { + { "layerName", command[1] } + }; + + string cmd = string.Format("zoneClearLayer:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#zonesetplantspeed") + { + bool err = false; + err = !int.TryParse(command[1], out int speed); + + Dictionary dictionary = new Dictionary() + { + { "speed", speed } + }; + + string cmd = string.Format("zoneSetPlantsSpeed:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#zonesetplantmode") + { + Dictionary dictionary = new Dictionary() + { + { "mode", command[1] } + }; + + string cmd = string.Format("zoneSetPlantsMode:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#currentzonerestoreoriginalgamma") + { + string cmd = string.Format("zoneRestoreOriginalGamma:zone_{0}:null", sender.ZoneId); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#zonedrawblockingbydefinition") + { + bool err = false; + err = !int.TryParse(command[1], out int def); + int[] defs = new int[1]; + defs[0] = def; + + Dictionary dictionary = new Dictionary() + { + { "definition", defs } + }; + + string cmd = string.Format("zoneDrawBlockingByDefinition:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + if (command[0] == "#addblockingtotiles") + { + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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. + } + } + + channel.SendMessageToAll(sessionManager, sender, string.Format("Added Blocking To {0} Tiles.", lockedtiles.Count)); + } + + if (command[0] == "#removeblockingfromtiles") + { + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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. + } + } + + channel.SendMessageToAll(sessionManager, sender, string.Format("Removed Blocking From {0} Tiles.", lockedtiles.Count)); + } + + if (command[0] == "#zonedecorlock") + { + + bool err = false; + err = !int.TryParse(command[1], out int id); + err = !int.TryParse(command[2], out int locked); + + Dictionary dictionary = new Dictionary() + { + { "ID", id }, + { "locked", locked } + }; + + string cmd = string.Format("zoneDecorLock:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + + } + + if (command[0] == "#zonetileshighway") + { + bool.TryParse(command[1], out bool adddelete); + bool.TryParse(command[2], out bool keeplock); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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.Highway = adddelete; + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (Highway)", lockedtiles.Count)); + } + + if (command[0] == "#zonetilesconcretea") + { + bool.TryParse(command[1], out bool adddelete); + bool.TryParse(command[2], out bool keeplock); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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.ConcreteA = adddelete; + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (ConcreteA)", lockedtiles.Count)); + } + + if (command[0] == "#zonetilesconcreteb") + { + + bool.TryParse(command[1], out bool adddelete); + bool.TryParse(command[2], out bool keeplock); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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.ConcreteB = adddelete; + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (ConcreteB)", lockedtiles.Count)); + } + + if (command[0] == "#zonetilesroaming") + { + + bool.TryParse(command[1], out bool adddelete); + bool.TryParse(command[2], out bool keeplock); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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.Roaming = adddelete; + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (Roaming)", lockedtiles.Count)); + } + + } + } +} diff --git a/src/Perpetuum/Services/Channels/IChannelManager.cs b/src/Perpetuum/Services/Channels/IChannelManager.cs index 8098be9f2..878933d00 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,7 @@ 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 KickOrBan(string channelName, Character issuer, Character character, string message, bool ban); void UnBan(string channelName, Character issuer, Character character); diff --git a/src/Perpetuum/Services/Sessions/Session.cs b/src/Perpetuum/Services/Sessions/Session.cs index 50cd28f5a..913dfc95d 100644 --- a/src/Perpetuum/Services/Sessions/Session.cs +++ b/src/Perpetuum/Services/Sessions/Session.cs @@ -27,9 +27,13 @@ public interface ISession bool IsAuthenticated { get; } Character Character { get; } AccessLevel AccessLevel { get; } + IZoneManager ZoneMgr { get; } 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(); @@ -101,6 +105,14 @@ public void Start() _connection.Receive(); } + public IZoneManager ZoneMgr + { + get + { + return _zoneManager; + } + } + public SessionID Id { get; private set; } public int AccountId { get; private set; } @@ -312,6 +324,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 +345,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/Zones/Locking/LockHandler.cs b/src/Perpetuum/Zones/Locking/LockHandler.cs index cebbfb070..e6c139678 100644 --- a/src/Perpetuum/Zones/Locking/LockHandler.cs +++ b/src/Perpetuum/Zones/Locking/LockHandler.cs @@ -151,8 +151,9 @@ private void ProcessNewLocks(IList locks) { if (locks.Any(l => l.Equals(newLock))) continue; - - if (locks.Count >= MaxLockedTargets) + // 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; From 1331d567a107c806ff2e2c5e1cc4eb4e9c5bc022 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 29 Jan 2018 18:59:18 -0500 Subject: [PATCH 020/457] add a windows service for the server. (#15) --- Perpetuum.sln | 14 ++ .../PerpetuumBootstrapper.cs | 5 + src/Perpetuum.ServerService/App.config | 18 +++ .../PerpServer.Designer.cs | 37 ++++++ src/Perpetuum.ServerService/PerpServer.cs | 81 ++++++++++++ .../Perpetuum.ServerService.csproj | 125 ++++++++++++++++++ src/Perpetuum.ServerService/Program.cs | 55 ++++++++ .../Properties/AssemblyInfo.cs | 36 +++++ .../Properties/Resources.Designer.cs | 63 +++++++++ .../Properties/Resources.resx | 120 +++++++++++++++++ .../Properties/Settings.Designer.cs | 35 +++++ .../Properties/Settings.settings | 9 ++ src/Perpetuum.ServerService/packages.config | 4 + 13 files changed, 602 insertions(+) create mode 100644 src/Perpetuum.ServerService/App.config create mode 100644 src/Perpetuum.ServerService/PerpServer.Designer.cs create mode 100644 src/Perpetuum.ServerService/PerpServer.cs create mode 100644 src/Perpetuum.ServerService/Perpetuum.ServerService.csproj create mode 100644 src/Perpetuum.ServerService/Program.cs create mode 100644 src/Perpetuum.ServerService/Properties/AssemblyInfo.cs create mode 100644 src/Perpetuum.ServerService/Properties/Resources.Designer.cs create mode 100644 src/Perpetuum.ServerService/Properties/Resources.resx create mode 100644 src/Perpetuum.ServerService/Properties/Settings.Designer.cs create mode 100644 src/Perpetuum.ServerService/Properties/Settings.settings create mode 100644 src/Perpetuum.ServerService/packages.config 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/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 137cd9ab7..7cdbd4aaa 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -218,6 +218,11 @@ public void Stop(TimeSpan delay) m.Shutdown(delay); } + public IContainer GetContainer() + { + return _container; + } + public void WaitForStop() { var are = new AutoResetEvent(false); 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..2c7eb4e06 --- /dev/null +++ b/src/Perpetuum.ServerService/PerpServer.cs @@ -0,0 +1,81 @@ +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; + +namespace Perpetuum.ServerService +{ + public partial class PerpServer : ServiceBase + { + + public PerpServer() + { + InitializeComponent(); + Bootstrapper = new PerpetuumBootstrapper(); + base.CanShutdown = true; + base.CanStop = true; + } + + PerpetuumBootstrapper Bootstrapper { get; set; } + + protected override void OnStart(string[] args) + { + // assumes the server is in the default installation directory. + string gameroot = Properties.Settings.Default.GameRoot; + + try + { + Bootstrapper.Init(gameroot); + } + catch + { + // we failed to init. no idea why. we need to log this. + // usual reasons are db connection failures because we are running as a user with no privs. + return; + } + + // start a task for our server. + Task task = Task.Run((Action)StartServer); + + } + + protected override void OnStop() + { + StopServer(); + } + + // windows requests this when shutting down. + // need to make sure this is called. + protected override void OnShutdown() + { + StopServer(); + } + + public void StartServer() + { + Bootstrapper.Start(); + Bootstrapper.WaitForStop(); + } + + private void StopServer() + { + Autofac.IContainer _container = Bootstrapper.GetContainer(); + + Bootstrapper.Stop(); + + // get the host container and check it's status. + // if we are running still or stopping we ask windows to not kill us. + var s = _container.Resolve(); + while (s.State == HostState.Online || s.State == HostState.Stopping) + { + RequestAdditionalTime(10000); // ask for more time. Hope we get it. + } + Thread.Sleep(10000); // wait 10 seconds for their logging process to finish. + } + } +} diff --git a/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj b/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj new file mode 100644 index 000000000..08acc9fcd --- /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 + + + AnyCPU + 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 From dd7c9df983b94833d02c82b38b623288e17c1520 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Tue, 30 Jan 2018 10:55:01 -0500 Subject: [PATCH 021/457] Fix for shutdown cancel issue (#48) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- src/Perpetuum/Host/HostShutDownManager.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 7cdbd4aaa..8648aaf25 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1582,7 +1582,7 @@ private void InitRelayManager() _builder.RegisterType().SingleInstance().As(); - _builder.RegisterType(); + _builder.RegisterType().SingleInstance(); _builder.RegisterType().As(); _builder.RegisterType(); 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); } From c8ea122733b6b62513b8ad2d8acc22eba05c69bf Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Tue, 30 Jan 2018 21:40:47 -0500 Subject: [PATCH 022/457] improvements to commands / teleporting. --- .../Services/Channels/GameAdminCommands.cs | 127 ++++++++++++++++-- .../Services/Sessions/ISessionManager.cs | 1 + .../Services/Sessions/SessionManager.cs | 11 ++ .../Strategies/TeleportToAnotherZone.cs | 9 +- 4 files changed, 137 insertions(+), 11 deletions(-) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index cf3f5e054..8d9c25670 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -3,7 +3,9 @@ using Perpetuum.Host.Requests; using Perpetuum.Services.Sessions; 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; @@ -78,22 +80,47 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C { bool err = false; err = !int.TryParse(command[1], out int characterID); - err = !int.TryParse(command[2], out int x); - err = !int.TryParse(command[3], out int y); + err = !int.TryParse(command[2], out int zoneID); + err = !int.TryParse(command[3], out int x); + err = !int.TryParse(command[4], out int y); if (err) { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } + + // get the target character's session. + var charactersession = sessionManager.GetByCharacter(characterID); - Dictionary dictionary = new Dictionary() + if (charactersession.Character.ZoneId == null) { - { "characterID" , characterID }, - { "x" , x }, - { "y" , y } - }; + channel.SendMessageToAll(sessionManager, sender, string.Format("ERR: Character with ID {0} does not have a zone. Are they docked?", characterID)); + return; + } + + // get destination zone. + var zone = request.Session.ZoneMgr.GetZone(zoneID); - string cmd = string.Format("zoneMoveUnit:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + if (charactersession.Character.ZoneId == null) + { + channel.SendMessageToAll(sessionManager, sender, 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 + var player = 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); + tp = null; + + channel.SendMessageToAll(sessionManager, sender, string.Format("Moved Character {0}-{1} to Zone {2} @ {3},{4}", characterID, charactersession.Character.Nick, zone.Id, x, y)); } if (command[0] == "#currentzonecleanobstacleblocking") @@ -488,6 +515,88 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (Roaming)", lockedtiles.Count)); } + if (command[0] == "#zonetilesPBSTerraformProtected") + { + + bool.TryParse(command[1], out bool adddelete); + bool.TryParse(command[2], out bool keeplock); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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.PBSTerraformProtected = adddelete; + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (PBSTerraformProtected)", lockedtiles.Count)); + } + + if (command[0] == "#getlockedtileproperties") + { + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var lockedtile = player.GetPrimaryLock(); + + TerrainControlInfo ti = zone.Terrain.Controls.GetValue((lockedtile as TerrainLock).Location); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Tile at {0},{1} has the following flags..", (lockedtile as TerrainLock).Location.X, (lockedtile as TerrainLock).Location.Y)); + channel.SendMessageToAll(sessionManager, sender, "TerrainControlFlags:"); + foreach (TerrainControlFlags f in Enum.GetValues(typeof(TerrainControlFlags))) + { + if (ti.Flags.HasFlag(f) && f != TerrainControlFlags.Undefined) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + BlockingInfo bi = zone.Terrain.Blocks.GetValue((lockedtile as TerrainLock).Location); + + channel.SendMessageToAll(sessionManager, sender, "BlockingFlags:"); + foreach (BlockingFlags f in Enum.GetValues(typeof(BlockingFlags))) + { + if (bi.Flags.HasFlag(f) && f != BlockingFlags.Undefined) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + PlantInfo pi = zone.Terrain.Plants.GetValue((lockedtile as TerrainLock).Location); + + channel.SendMessageToAll(sessionManager, sender, "PlantType:"); + foreach (PlantType f in Enum.GetValues(typeof(PlantType))) + { + if (pi.type.HasFlag(f) && f != PlantType.NotDefined) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + channel.SendMessageToAll(sessionManager, sender, "GroundType:"); + foreach (GroundType f in Enum.GetValues(typeof(GroundType))) + { + if (pi.groundType.HasFlag(f)) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + } } } } 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/SessionManager.cs b/src/Perpetuum/Services/Sessions/SessionManager.cs index 17569a73d..6a73d9d4e 100644 --- a/src/Perpetuum/Services/Sessions/SessionManager.cs +++ b/src/Perpetuum/Services/Sessions/SessionManager.cs @@ -169,6 +169,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/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs index 70481ad1b..d17df43d9 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs @@ -42,8 +42,13 @@ public void DoTeleport(Player player) [CanBeNull] public Task DoTeleportAsync(Player player) { - // FIXME: this needs to calculate a proper position but the server does not have the altitude data for this map - var validPosition = new Position(1120, 1039); + // 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; From 56071e822f031d13e4b1abeeadda3141f627f556 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 30 Jan 2018 23:27:22 -0500 Subject: [PATCH 023/457] release build x64 (#17) --- src/Perpetuum.ServerService/Perpetuum.ServerService.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj b/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj index 08acc9fcd..a226ff8d8 100644 --- a/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj +++ b/src/Perpetuum.ServerService/Perpetuum.ServerService.csproj @@ -38,7 +38,7 @@ 4 - AnyCPU + x64 pdbonly true bin\Release\ From a6f5abfdc5c34972707296cc1759b90603455b13 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Wed, 31 Jan 2018 20:58:16 -0500 Subject: [PATCH 024/457] improvements to the command interface --- .../Zone/StatsMapDrawing/ZoneDrawStatMap.cs | 6 +- src/Perpetuum/Players/Player.cs | 1 + src/Perpetuum/Services/Channels/Channel.cs | 25 +++- .../Services/Channels/ChannelManager.cs | 8 +- .../Services/Channels/ChannelRepository.cs | 5 +- .../Services/Channels/GameAdminCommands.cs | 127 +++++++++++++++++- src/Perpetuum/Units/Unit.Visibility.cs | 8 +- 7 files changed, 162 insertions(+), 18 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 6f8ffe41d..791b1f2d6 100644 --- a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs +++ b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs @@ -25,16 +25,15 @@ namespace Perpetuum.RequestHandlers.Zone.StatsMapDrawing { public partial class ZoneDrawStatMap : IRequestHandler { - private readonly IZone _zone; + private IZone _zone; private readonly IFileSystem _fileSystem; private readonly SaveBitmapHelper _saveBitmapHelper; private readonly MissionDataCache _missionDataCache; private readonly Dictionary> _actions = new Dictionary>(); private string _typeString; - 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; @@ -86,6 +85,7 @@ private void CreateAndSave(string postfix,Func bitmapFactory) public void HandleRequest(IZoneRequest request) { + _zone = request.Zone; var type = request.Data.GetOrDefault(k.type); _typeString = type; //save for later use diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 68472e8e5..203bcd4a8 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -156,6 +156,7 @@ public Player(IExtensionReader extensionReader,ICorporationManager corporationMa public long CorporationEid { get; set; } public IZoneSession Session { get; private set; } public Character Character { get; set; } = Character.None; + public bool HasGMStealth { get; set; } public void SetSession(IZoneSession session) { diff --git a/src/Perpetuum/Services/Channels/Channel.cs b/src/Perpetuum/Services/Channels/Channel.cs index 9384e08f6..cca52d9ac 100644 --- a/src/Perpetuum/Services/Channels/Channel.cs +++ b/src/Perpetuum/Services/Channels/Channel.cs @@ -6,7 +6,7 @@ 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; } @@ -19,17 +19,17 @@ public class Channel 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; @@ -61,7 +61,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) @@ -101,7 +101,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 { @@ -115,6 +115,19 @@ public Channel SetMember(ChannelMember member) }; } + public void SetAdmin(bool isadminchannel) + { + if (isadminchannel) + { + this.Type = ChannelType.Admin; + } + else + { + this.Type = ChannelType.Public; + } + } + + public Channel RemoveMember(Character member) { var members = new Dictionary(_members); diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index 45c6cc3b7..69902afdf 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -264,12 +264,12 @@ public void Talk(string channelName, Character sender, string message, IRequest m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); channel.SendMessageToAll(_sessionManager, sender, message); - // this is an admin or GM command sent on an admin channel. - if (message.Substring(0,1) == "#" && channel.Type == ChannelType.Admin) + // this is an admin or GM command. + if (message.Substring(0, 1) == "#" && sender.AccessLevel == AccessLevel.toolAdmin) { - channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager); + channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager, this); } - + } public void KickOrBan(string channelName, Character issuer, Character character, string message, bool ban) 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/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 8d9c25670..459f93872 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -1,7 +1,10 @@ using Perpetuum.Accounting.Characters; +using Perpetuum.ExportedTypes; using Perpetuum.GenXY; using Perpetuum.Host.Requests; +using Perpetuum.Players; using Perpetuum.Services.Sessions; +using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Teleporting.Strategies; using Perpetuum.Zones.Terrains; @@ -24,10 +27,26 @@ public GameAdminCommands() // obviously everything coming in from the in-game chat is a string. // we have to take that string and chop it up, work out what command is being executed // then parse/cast/convert arguments as necessary. - public void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, ISessionManager sessionManager) + public void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, ISessionManager sessionManager, ChannelManager channelmanager) { string[] command = text.Split(new char[] { ',' }); + // channel is not secured. must be secured first. + if (channel.Type != ChannelType.Admin) + { + + if (command[0] == "#secure") + { + channel.SetAdmin(true); + channel.SendMessageToAll(sessionManager, sender, "Channel Secured."); + return; + } + + channel.SendMessageToAll(sessionManager, sender, "Channel must be secured before sending commands."); + return; + } + + if (command[0] == "#shutdown") { DateTime shutdownin = DateTime.Now; @@ -122,7 +141,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Moved Character {0}-{1} to Zone {2} @ {3},{4}", characterID, charactersession.Character.Nick, zone.Id, x, y)); } - +#if DEBUG if (command[0] == "#currentzonecleanobstacleblocking") { string cmd = string.Format("zoneCleanObstacleBlocking:zone_{0}:null", sender.ZoneId); @@ -544,6 +563,28 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (PBSTerraformProtected)", lockedtiles.Count)); } +#endif + + if (command[0] == "#giveitem") + { + + int.TryParse(command[1], out int definition); + int.TryParse(command[2], out int qty); + + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "quantity", qty } + }; + + + string cmd = string.Format("createItem:relay:{0}", GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Gave Item {0} ", definition)); + } + + if (command[0] == "#getlockedtileproperties") { @@ -597,6 +638,88 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C } } + + if (command[0] == "#setvisibility") + { + + bool.TryParse(command[1], out bool visiblestate); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + player.HasGMStealth = !visiblestate; + + channel.SendMessageToAll(sessionManager, sender, string.Format("Player {0} visibility is {1}", player.Character.Nick, visiblestate)); + } + + if (command[0] == "#zonedrawstatmap") + { + + Dictionary dictionary = new Dictionary() + { + { "type", command[1] } + }; + + string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + + if (command[0] == "#listplayersinzone") + { + + int.TryParse(command[1], out int zoneid); + + var zone = request.Session.ZoneMgr.GetZone((int)zoneid); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}", zone.Id)); + channel.SendMessageToAll(sessionManager, sender, string.Format(" AccountId CharacterId Nick Access Level Docked? DockedAt Position")); + foreach (Character c in sessionManager.SelectedCharacters) + { + channel.SendMessageToAll(sessionManager, sender, string.Format(" {0} {1} {2} {3} {4} {5} {6}", + c.AccountId, c.Id, c.Nick, c.AccessLevel, c.IsDocked, c.GetCurrentDockingBase().Eid, c.ZonePosition)); + } + } + + if (command[0] == "#countofplayers") + { + foreach (IZone z in request.Session.ZoneMgr.Zones) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}: {1}", z.Id, z.Players.ToList().Count)); + } + } + + if (command[0] == "#unsecure") + { + channel.SetAdmin(false); + channel.SendMessageToAll(sessionManager, sender, "Channel is now public."); + } + + if (command[0] == "#addtochannel") + { + int.TryParse(command[1], out int characterid); + + var c = sessionManager.GetByCharacter(characterid); + + channelmanager.JoinChannel(channel.Name, c.Character, ChannelMemberRole.Operator, string.Empty); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Added character {0} to channel ", c.Character.Nick)); + + } + + if (command[0] == "#removefromchannel") + { + int.TryParse(command[1], out int characterid); + + var c = sessionManager.GetByCharacter(characterid); + + channelmanager.LeaveChannel(channel.Name, c.Character); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Removed character {0} from channel ", c.Character.Nick)); + + } + } } } diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index 00475b041..acec40221 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -57,8 +57,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; From 58fc15a834f4fe44b4f0c592b9fa856737195aab Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Fri, 2 Feb 2018 23:35:24 -0500 Subject: [PATCH 025/457] we have to teleport someplace! --- src/Perpetuum/Services/RiftSystem/Rift.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index e0c710fae..55bf41b01 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -185,6 +185,7 @@ public void UseItem(Player player) if (this.IsDestinationStronghold && destination != null) { var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destination); + teleport.TargetPosition = new Position(1120, 1039); teleport.DoTeleportAsync(player); } else From ed44946371d4afeba93205e7a2ead91cf9bbab45 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 3 Feb 2018 13:00:28 -0500 Subject: [PATCH 026/457] rift system + stronghold overhaul. --- .../PerpetuumBootstrapper.cs | 2 + src/Perpetuum/Modules/Weapons/WeaponModule.cs | 6 +++ src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Services/RiftSystem/Rift.cs | 30 +++++++++++--- .../Services/RiftSystem/RiftManager.cs | 40 ++++++++++++++----- src/Perpetuum/Zones/StrongHoldZone.cs | 17 ++++++++ src/Perpetuum/Zones/ZoneType.cs | 3 +- 7 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 src/Perpetuum/Zones/StrongHoldZone.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 7cdbd4aaa..8a4a95651 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2354,6 +2354,8 @@ void RegisterZone(ZoneType type) where T:Zone RegisterZone(ZoneType.Pve); RegisterZone(ZoneType.Pvp); RegisterZone(ZoneType.Training); + RegisterZone(ZoneType.Stronghold); + _builder.RegisterType(); _builder.RegisterType(); diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 2c4d80c14..6b969fdac 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Services.RiftSystem; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -77,6 +78,11 @@ public void VisitUnitLock(UnitLock unitLock) victim.IsInvulnerable.ThrowIfTrue(ErrorCodes.TargetIsInvulnerable); + if (victim is Rift && (_weapon.ParentRobot.Zone is StrongHoldZone)) + { + throw new PerpetuumException(ErrorCodes.TargetIsNonAttackable); + } + _weapon.ConsumeAmmo(); var result = _weapon.GetLineOfSight(victim); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 957942098..1aa75cab3 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -842,6 +842,7 @@ + diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index 55bf41b01..f4579ed18 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -12,6 +12,7 @@ using Perpetuum.Zones; using Perpetuum.Zones.Blobs.BlobEmitters; using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.Teleporting; using Perpetuum.Zones.Teleporting.Strategies; namespace Perpetuum.Services.RiftSystem @@ -131,7 +132,8 @@ public Rift(ITeleportStrategyFactories teleportStrategyFactories) _blobEmitter = new BlobEmitter(this); } - public bool IsDestinationStronghold { get; set; } = false; + public int DestinationStrongholdZone { get; set; } + public int OriginZone { get; set; } public void SetDespawnTime(TimeSpan despawnTime) { @@ -178,14 +180,30 @@ public void UseItem(Player player) player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - IZone destination = player.Character.GetZone(16); + + // we are on a stronghold. we want to go home. + // for the moment we will send them to a teleporter on zone 0. + if (player.Zone is StrongHoldZone) + { + var destZone = player.Character.GetZone(0); + var teleportColumns = destZone.GetTeleportColumns().Where(t => t.IsEnabled); + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); + teleport.TargetPosition = teleportColumns.First().CurrentPosition; + teleport.DoTeleportAsync(player); + return; + } // teleport player to stronghold // stronghold zone must be active. - if (this.IsDestinationStronghold && destination != null) + if (this.DestinationStrongholdZone > 0) { - var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destination); - teleport.TargetPosition = new Position(1120, 1039); + var destZone = player.Character.GetZone(DestinationStrongholdZone); + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); + // there should only be one, for now. + // FIXME: some zones may have more than one stronghold. + // we will need to link anomalies like teleporters. + var pos = destZone.Units.OfType().First(); + teleport.TargetPosition = pos.CurrentPosition; teleport.DoTeleportAsync(player); } else @@ -194,7 +212,9 @@ public void UseItem(Player player) var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); if (nearestRift == null) + { throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); teleport.TargetPosition = nearestRift.CurrentPosition; diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 489f3ef73..cbe99cf50 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Linq; using System.Threading; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -61,17 +62,19 @@ public class RiftManager private readonly TimeRange _spawnTime; private readonly RiftSpawnPositionFinder _spawnPositionFinder; private readonly IEntityServices _entityServices; + private readonly ZoneManager _zonemanager; private bool StrongHoldRiftGenerated { get; set; } = false; private Random r; 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, ZoneManager zoneManager) { _zone = zone; _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; + _zonemanager = zoneManager; r = new Random(); } @@ -79,9 +82,7 @@ public RiftManager(IZone zone,TimeRange spawnTime,RiftSpawnPositionFinder spawnP public void Update(TimeSpan time) { - // FIXME: this needs to be in the database. - // do not spawn rifts on zone 16 (the stronghold) - while (_riftCounts < 10 && _zone.Id != 16) + while (_riftCounts < 10 && !(_zone is StrongHoldZone)) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); @@ -98,6 +99,23 @@ public void Update(TimeSpan time) }); } + /// + /// Get a random active stronghold zone. + /// + /// int + private int GetRandomStrongHoldZone() + { + IEnumerable strongholdzones = _zonemanager.Zones.OfType(); + // less than elegant. if we only have one stronghold zone just return it. + if (strongholdzones.Count() == 1) + { + return strongholdzones.ElementAt(0).Id; + } + int index = r.Next(0, strongholdzones.Count()); + Zone selzone = strongholdzones.ElementAt(index); + return selzone.Id; + } + private void SpawnRift() { var rift = (Rift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.RIFT); @@ -106,15 +124,17 @@ private void SpawnRift() rift.RemovedFromZone += OnRiftRemovedFromZone; // only generate one stronghold teleport and make it random chance. - int rand = r.Next(0, 10); - if (rand == 2 && !StrongHoldRiftGenerated) - { - rift.IsDestinationStronghold = true; - } + //int rand = r.Next(0, 10); + //if (rand == 2 && !StrongHoldRiftGenerated) + //{ + rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); + rift.OriginZone = this._zone.Id; + //StrongHoldRiftGenerated = true; + //} var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); - Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ") Stronghold:" + rift.IsDestinationStronghold); + Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2}) Stronghold Zone ID: {3}", _zone.Id, rift.ED.Name, rift.CurrentPosition, rift.DestinationStrongholdZone)); } private void OnRiftRemovedFromZone(Unit unit) 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/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 From 91685961c1383674d19168c8f744e936d5c63252 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 3 Feb 2018 13:06:20 -0500 Subject: [PATCH 027/457] forgot to put back random chance. --- src/Perpetuum/Services/RiftSystem/RiftManager.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index cbe99cf50..2357f0488 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -123,14 +123,13 @@ private void SpawnRift() rift.SetDespawnTime(TimeSpan.FromHours(3)); rift.RemovedFromZone += OnRiftRemovedFromZone; - // only generate one stronghold teleport and make it random chance. - //int rand = r.Next(0, 10); - //if (rand == 2 && !StrongHoldRiftGenerated) - //{ + // generate a stronghold teleport and make it random chance. + int rand = r.Next(0, 10); + if (rand == 2 && !StrongHoldRiftGenerated) + { rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); rift.OriginZone = this._zone.Id; - //StrongHoldRiftGenerated = true; - //} + } var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); From 37a89ae83b91b52d1e1ed1bdbaeb2e76b2fc3e35 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 3 Feb 2018 21:34:50 -0500 Subject: [PATCH 028/457] more command updates. --- .../Services/Channels/GameAdminCommands.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 459f93872..b81454641 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -3,6 +3,7 @@ using Perpetuum.GenXY; using Perpetuum.Host.Requests; using Perpetuum.Players; +using Perpetuum.Services.RiftSystem; using Perpetuum.Services.Sessions; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; @@ -44,8 +45,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, "Channel must be secured before sending commands."); return; - } - + } if (command[0] == "#shutdown") { @@ -720,6 +720,32 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C } + if (command[0] == "#listrifts") + { + foreach (IZone z in request.Session.ZoneMgr.Zones) + { + var rift = z.Units.OfType(); + foreach (Rift r in rift) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("Rift - Zone: {0}, Position: ({1}), Destination Zone:{2}", r.Zone, r.CurrentPosition, r.DestinationStrongholdZone)); + } + } + } + + + if (command[0] == "#flagplayernameoffensive") + { + bool err = false; + err = !int.TryParse(command[1], out int characterID); + err = !bool.TryParse(command[2], out bool isoffensive); + + var charactersession = sessionManager.GetByCharacter(characterID); + charactersession.Character.IsOffensiveNick = isoffensive; + + channel.SendMessageToAll(sessionManager, sender, string.Format("Player with nick {0} is offensive:{1}", charactersession.Character.Nick, charactersession.Character.IsOffensiveNick)); + + } + } } } From 569fde2b9d43a75b149c30c69c501e2c52a3a4af Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 3 Feb 2018 21:44:47 -0500 Subject: [PATCH 029/457] Tutorial Fix -- force send to TMA (#19) --- .../Zones/Teleporting/Strategies/TrainingExitStrategy.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index 66fad4851..4af8e3f77 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -71,7 +71,9 @@ 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); From 5e887056f864418a8b05bffc98f3fb0ddbbb4e76 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 3 Feb 2018 21:45:01 -0500 Subject: [PATCH 030/457] Loyal dev (#20) * improvements to commands / teleporting. * improvements to the command interface * we have to teleport someplace! * rift system + stronghold overhaul. * forgot to put back random chance. * more command updates. --- .../PerpetuumBootstrapper.cs | 2 + .../Zone/StatsMapDrawing/ZoneDrawStatMap.cs | 6 +- src/Perpetuum/Modules/Weapons/WeaponModule.cs | 6 + src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Players/Player.cs | 1 + src/Perpetuum/Services/Channels/Channel.cs | 25 +- .../Services/Channels/ChannelManager.cs | 8 +- .../Services/Channels/ChannelRepository.cs | 5 +- .../Services/Channels/GameAdminCommands.cs | 280 +++++++++++++++++- src/Perpetuum/Services/RiftSystem/Rift.cs | 29 +- .../Services/RiftSystem/RiftManager.cs | 33 ++- .../Services/Sessions/ISessionManager.cs | 1 + .../Services/Sessions/SessionManager.cs | 11 + src/Perpetuum/Units/Unit.Visibility.cs | 8 +- src/Perpetuum/Zones/StrongHoldZone.cs | 17 ++ .../Strategies/TeleportToAnotherZone.cs | 9 +- src/Perpetuum/Zones/ZoneType.cs | 3 +- 17 files changed, 404 insertions(+), 41 deletions(-) create mode 100644 src/Perpetuum/Zones/StrongHoldZone.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 8648aaf25..1e3e77e81 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2354,6 +2354,8 @@ void RegisterZone(ZoneType type) where T:Zone RegisterZone(ZoneType.Pve); RegisterZone(ZoneType.Pvp); RegisterZone(ZoneType.Training); + RegisterZone(ZoneType.Stronghold); + _builder.RegisterType(); _builder.RegisterType(); diff --git a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 6f8ffe41d..791b1f2d6 100644 --- a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs +++ b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs @@ -25,16 +25,15 @@ namespace Perpetuum.RequestHandlers.Zone.StatsMapDrawing { public partial class ZoneDrawStatMap : IRequestHandler { - private readonly IZone _zone; + private IZone _zone; private readonly IFileSystem _fileSystem; private readonly SaveBitmapHelper _saveBitmapHelper; private readonly MissionDataCache _missionDataCache; private readonly Dictionary> _actions = new Dictionary>(); private string _typeString; - 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; @@ -86,6 +85,7 @@ private void CreateAndSave(string postfix,Func bitmapFactory) public void HandleRequest(IZoneRequest request) { + _zone = request.Zone; var type = request.Data.GetOrDefault(k.type); _typeString = type; //save for later use diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 2c4d80c14..6b969fdac 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Services.RiftSystem; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -77,6 +78,11 @@ public void VisitUnitLock(UnitLock unitLock) victim.IsInvulnerable.ThrowIfTrue(ErrorCodes.TargetIsInvulnerable); + if (victim is Rift && (_weapon.ParentRobot.Zone is StrongHoldZone)) + { + throw new PerpetuumException(ErrorCodes.TargetIsNonAttackable); + } + _weapon.ConsumeAmmo(); var result = _weapon.GetLineOfSight(victim); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 957942098..1aa75cab3 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -842,6 +842,7 @@ + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 68472e8e5..203bcd4a8 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -156,6 +156,7 @@ public Player(IExtensionReader extensionReader,ICorporationManager corporationMa public long CorporationEid { get; set; } public IZoneSession Session { get; private set; } public Character Character { get; set; } = Character.None; + public bool HasGMStealth { get; set; } public void SetSession(IZoneSession session) { diff --git a/src/Perpetuum/Services/Channels/Channel.cs b/src/Perpetuum/Services/Channels/Channel.cs index 9384e08f6..cca52d9ac 100644 --- a/src/Perpetuum/Services/Channels/Channel.cs +++ b/src/Perpetuum/Services/Channels/Channel.cs @@ -6,7 +6,7 @@ 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; } @@ -19,17 +19,17 @@ public class Channel 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; @@ -61,7 +61,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) @@ -101,7 +101,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 { @@ -115,6 +115,19 @@ public Channel SetMember(ChannelMember member) }; } + public void SetAdmin(bool isadminchannel) + { + if (isadminchannel) + { + this.Type = ChannelType.Admin; + } + else + { + this.Type = ChannelType.Public; + } + } + + public Channel RemoveMember(Character member) { var members = new Dictionary(_members); diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index 45c6cc3b7..69902afdf 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -264,12 +264,12 @@ public void Talk(string channelName, Character sender, string message, IRequest m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); channel.SendMessageToAll(_sessionManager, sender, message); - // this is an admin or GM command sent on an admin channel. - if (message.Substring(0,1) == "#" && channel.Type == ChannelType.Admin) + // this is an admin or GM command. + if (message.Substring(0, 1) == "#" && sender.AccessLevel == AccessLevel.toolAdmin) { - channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager); + channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager, this); } - + } public void KickOrBan(string channelName, Character issuer, Character character, string message, bool ban) 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/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index cf3f5e054..b81454641 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -1,9 +1,15 @@ using Perpetuum.Accounting.Characters; +using Perpetuum.ExportedTypes; using Perpetuum.GenXY; using Perpetuum.Host.Requests; +using Perpetuum.Players; +using Perpetuum.Services.RiftSystem; using Perpetuum.Services.Sessions; +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; @@ -22,10 +28,25 @@ public GameAdminCommands() // obviously everything coming in from the in-game chat is a string. // we have to take that string and chop it up, work out what command is being executed // then parse/cast/convert arguments as necessary. - public void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, ISessionManager sessionManager) + public void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, ISessionManager sessionManager, ChannelManager channelmanager) { string[] command = text.Split(new char[] { ',' }); + // channel is not secured. must be secured first. + if (channel.Type != ChannelType.Admin) + { + + if (command[0] == "#secure") + { + channel.SetAdmin(true); + channel.SendMessageToAll(sessionManager, sender, "Channel Secured."); + return; + } + + channel.SendMessageToAll(sessionManager, sender, "Channel must be secured before sending commands."); + return; + } + if (command[0] == "#shutdown") { DateTime shutdownin = DateTime.Now; @@ -78,24 +99,49 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C { bool err = false; err = !int.TryParse(command[1], out int characterID); - err = !int.TryParse(command[2], out int x); - err = !int.TryParse(command[3], out int y); + err = !int.TryParse(command[2], out int zoneID); + err = !int.TryParse(command[3], out int x); + err = !int.TryParse(command[4], out int y); if (err) { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } + + // get the target character's session. + var charactersession = sessionManager.GetByCharacter(characterID); - Dictionary dictionary = new Dictionary() + if (charactersession.Character.ZoneId == null) { - { "characterID" , characterID }, - { "x" , x }, - { "y" , y } - }; + channel.SendMessageToAll(sessionManager, sender, string.Format("ERR: Character with ID {0} does not have a zone. Are they docked?", characterID)); + return; + } + + // get destination zone. + var zone = request.Session.ZoneMgr.GetZone(zoneID); - string cmd = string.Format("zoneMoveUnit:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } + if (charactersession.Character.ZoneId == null) + { + channel.SendMessageToAll(sessionManager, sender, 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 + var player = 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); + tp = null; + + channel.SendMessageToAll(sessionManager, sender, string.Format("Moved Character {0}-{1} to Zone {2} @ {3},{4}", characterID, charactersession.Character.Nick, zone.Id, x, y)); + } +#if DEBUG if (command[0] == "#currentzonecleanobstacleblocking") { string cmd = string.Format("zoneCleanObstacleBlocking:zone_{0}:null", sender.ZoneId); @@ -488,6 +534,218 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (Roaming)", lockedtiles.Count)); } + if (command[0] == "#zonetilesPBSTerraformProtected") + { + + bool.TryParse(command[1], out bool adddelete); + bool.TryParse(command[2], out bool keeplock); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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.PBSTerraformProtected = adddelete; + zone.Terrain.Controls.SetValue(pos, ti); + if (!keeplock) + { + item.Cancel(); // cancel this lock. we processed it. + } + } + } + channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (PBSTerraformProtected)", lockedtiles.Count)); + } + +#endif + + if (command[0] == "#giveitem") + { + + int.TryParse(command[1], out int definition); + int.TryParse(command[2], out int qty); + + Dictionary dictionary = new Dictionary() + { + { "definition", definition }, + { "quantity", qty } + }; + + + string cmd = string.Format("createItem:relay:{0}", GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Gave Item {0} ", definition)); + } + + + if (command[0] == "#getlockedtileproperties") + { + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var lockedtile = player.GetPrimaryLock(); + + TerrainControlInfo ti = zone.Terrain.Controls.GetValue((lockedtile as TerrainLock).Location); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Tile at {0},{1} has the following flags..", (lockedtile as TerrainLock).Location.X, (lockedtile as TerrainLock).Location.Y)); + channel.SendMessageToAll(sessionManager, sender, "TerrainControlFlags:"); + foreach (TerrainControlFlags f in Enum.GetValues(typeof(TerrainControlFlags))) + { + if (ti.Flags.HasFlag(f) && f != TerrainControlFlags.Undefined) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + BlockingInfo bi = zone.Terrain.Blocks.GetValue((lockedtile as TerrainLock).Location); + + channel.SendMessageToAll(sessionManager, sender, "BlockingFlags:"); + foreach (BlockingFlags f in Enum.GetValues(typeof(BlockingFlags))) + { + if (bi.Flags.HasFlag(f) && f != BlockingFlags.Undefined) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + PlantInfo pi = zone.Terrain.Plants.GetValue((lockedtile as TerrainLock).Location); + + channel.SendMessageToAll(sessionManager, sender, "PlantType:"); + foreach (PlantType f in Enum.GetValues(typeof(PlantType))) + { + if (pi.type.HasFlag(f) && f != PlantType.NotDefined) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + channel.SendMessageToAll(sessionManager, sender, "GroundType:"); + foreach (GroundType f in Enum.GetValues(typeof(GroundType))) + { + if (pi.groundType.HasFlag(f)) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); + } + } + + } + + if (command[0] == "#setvisibility") + { + + bool.TryParse(command[1], out bool visiblestate); + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + player.HasGMStealth = !visiblestate; + + channel.SendMessageToAll(sessionManager, sender, string.Format("Player {0} visibility is {1}", player.Character.Nick, visiblestate)); + } + + if (command[0] == "#zonedrawstatmap") + { + + Dictionary dictionary = new Dictionary() + { + { "type", command[1] } + }; + + string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + + + if (command[0] == "#listplayersinzone") + { + + int.TryParse(command[1], out int zoneid); + + var zone = request.Session.ZoneMgr.GetZone((int)zoneid); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}", zone.Id)); + channel.SendMessageToAll(sessionManager, sender, string.Format(" AccountId CharacterId Nick Access Level Docked? DockedAt Position")); + foreach (Character c in sessionManager.SelectedCharacters) + { + channel.SendMessageToAll(sessionManager, sender, string.Format(" {0} {1} {2} {3} {4} {5} {6}", + c.AccountId, c.Id, c.Nick, c.AccessLevel, c.IsDocked, c.GetCurrentDockingBase().Eid, c.ZonePosition)); + } + } + + if (command[0] == "#countofplayers") + { + foreach (IZone z in request.Session.ZoneMgr.Zones) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}: {1}", z.Id, z.Players.ToList().Count)); + } + } + + if (command[0] == "#unsecure") + { + channel.SetAdmin(false); + channel.SendMessageToAll(sessionManager, sender, "Channel is now public."); + } + + if (command[0] == "#addtochannel") + { + int.TryParse(command[1], out int characterid); + + var c = sessionManager.GetByCharacter(characterid); + + channelmanager.JoinChannel(channel.Name, c.Character, ChannelMemberRole.Operator, string.Empty); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Added character {0} to channel ", c.Character.Nick)); + + } + + if (command[0] == "#removefromchannel") + { + int.TryParse(command[1], out int characterid); + + var c = sessionManager.GetByCharacter(characterid); + + channelmanager.LeaveChannel(channel.Name, c.Character); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Removed character {0} from channel ", c.Character.Nick)); + + } + + if (command[0] == "#listrifts") + { + foreach (IZone z in request.Session.ZoneMgr.Zones) + { + var rift = z.Units.OfType(); + foreach (Rift r in rift) + { + channel.SendMessageToAll(sessionManager, sender, string.Format("Rift - Zone: {0}, Position: ({1}), Destination Zone:{2}", r.Zone, r.CurrentPosition, r.DestinationStrongholdZone)); + } + } + } + + + if (command[0] == "#flagplayernameoffensive") + { + bool err = false; + err = !int.TryParse(command[1], out int characterID); + err = !bool.TryParse(command[2], out bool isoffensive); + + var charactersession = sessionManager.GetByCharacter(characterID); + charactersession.Character.IsOffensiveNick = isoffensive; + + channel.SendMessageToAll(sessionManager, sender, string.Format("Player with nick {0} is offensive:{1}", charactersession.Character.Nick, charactersession.Character.IsOffensiveNick)); + + } + } } } diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index e0c710fae..f4579ed18 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -12,6 +12,7 @@ using Perpetuum.Zones; using Perpetuum.Zones.Blobs.BlobEmitters; using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.Teleporting; using Perpetuum.Zones.Teleporting.Strategies; namespace Perpetuum.Services.RiftSystem @@ -131,7 +132,8 @@ public Rift(ITeleportStrategyFactories teleportStrategyFactories) _blobEmitter = new BlobEmitter(this); } - public bool IsDestinationStronghold { get; set; } = false; + public int DestinationStrongholdZone { get; set; } + public int OriginZone { get; set; } public void SetDespawnTime(TimeSpan despawnTime) { @@ -178,13 +180,30 @@ public void UseItem(Player player) player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - IZone destination = player.Character.GetZone(16); + + // we are on a stronghold. we want to go home. + // for the moment we will send them to a teleporter on zone 0. + if (player.Zone is StrongHoldZone) + { + var destZone = player.Character.GetZone(0); + var teleportColumns = destZone.GetTeleportColumns().Where(t => t.IsEnabled); + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); + teleport.TargetPosition = teleportColumns.First().CurrentPosition; + teleport.DoTeleportAsync(player); + return; + } // teleport player to stronghold // stronghold zone must be active. - if (this.IsDestinationStronghold && destination != null) + if (this.DestinationStrongholdZone > 0) { - var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destination); + var destZone = player.Character.GetZone(DestinationStrongholdZone); + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); + // there should only be one, for now. + // FIXME: some zones may have more than one stronghold. + // we will need to link anomalies like teleporters. + var pos = destZone.Units.OfType().First(); + teleport.TargetPosition = pos.CurrentPosition; teleport.DoTeleportAsync(player); } else @@ -193,7 +212,9 @@ public void UseItem(Player player) var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); if (nearestRift == null) + { throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); teleport.TargetPosition = nearestRift.CurrentPosition; diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 489f3ef73..2357f0488 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Linq; using System.Threading; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -61,17 +62,19 @@ public class RiftManager private readonly TimeRange _spawnTime; private readonly RiftSpawnPositionFinder _spawnPositionFinder; private readonly IEntityServices _entityServices; + private readonly ZoneManager _zonemanager; private bool StrongHoldRiftGenerated { get; set; } = false; private Random r; 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, ZoneManager zoneManager) { _zone = zone; _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; + _zonemanager = zoneManager; r = new Random(); } @@ -79,9 +82,7 @@ public RiftManager(IZone zone,TimeRange spawnTime,RiftSpawnPositionFinder spawnP public void Update(TimeSpan time) { - // FIXME: this needs to be in the database. - // do not spawn rifts on zone 16 (the stronghold) - while (_riftCounts < 10 && _zone.Id != 16) + while (_riftCounts < 10 && !(_zone is StrongHoldZone)) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); @@ -98,6 +99,23 @@ public void Update(TimeSpan time) }); } + /// + /// Get a random active stronghold zone. + /// + /// int + private int GetRandomStrongHoldZone() + { + IEnumerable strongholdzones = _zonemanager.Zones.OfType(); + // less than elegant. if we only have one stronghold zone just return it. + if (strongholdzones.Count() == 1) + { + return strongholdzones.ElementAt(0).Id; + } + int index = r.Next(0, strongholdzones.Count()); + Zone selzone = strongholdzones.ElementAt(index); + return selzone.Id; + } + private void SpawnRift() { var rift = (Rift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.RIFT); @@ -105,16 +123,17 @@ private void SpawnRift() rift.SetDespawnTime(TimeSpan.FromHours(3)); rift.RemovedFromZone += OnRiftRemovedFromZone; - // only generate one stronghold teleport and make it random chance. + // generate a stronghold teleport and make it random chance. int rand = r.Next(0, 10); if (rand == 2 && !StrongHoldRiftGenerated) { - rift.IsDestinationStronghold = true; + rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); + rift.OriginZone = this._zone.Id; } var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); - Logger.Info("Rift spawned. " + rift.ED.Name + " (" + rift.CurrentPosition + ") Stronghold:" + rift.IsDestinationStronghold); + Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2}) Stronghold Zone ID: {3}", _zone.Id, rift.ED.Name, rift.CurrentPosition, rift.DestinationStrongholdZone)); } private void OnRiftRemovedFromZone(Unit unit) 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/SessionManager.cs b/src/Perpetuum/Services/Sessions/SessionManager.cs index 17569a73d..6a73d9d4e 100644 --- a/src/Perpetuum/Services/Sessions/SessionManager.cs +++ b/src/Perpetuum/Services/Sessions/SessionManager.cs @@ -169,6 +169,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/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index 00475b041..acec40221 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -57,8 +57,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; 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/Strategies/TeleportToAnotherZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs index 70481ad1b..d17df43d9 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportToAnotherZone.cs @@ -42,8 +42,13 @@ public void DoTeleport(Player player) [CanBeNull] public Task DoTeleportAsync(Player player) { - // FIXME: this needs to calculate a proper position but the server does not have the altitude data for this map - var validPosition = new Position(1120, 1039); + // 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; 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 From 816acf836262a51a3caa5f14d9495054fcf7b3ab Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Mon, 5 Feb 2018 12:32:07 -0500 Subject: [PATCH 031/457] return players in zone properly. --- .../Services/Channels/GameAdminCommands.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index b81454641..34c81ca7d 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -229,9 +229,9 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C Dictionary dictionary = new Dictionary() { { "definition", definition }, - { "x", x*255 }, - { "y", y*255 }, - { "z", z }, + { "x", x*256 }, + { "y", y*256 }, + { "z", z*256 }, { "quaternionX", qx }, { "quaternionY", qy }, { "quaternionZ", qz }, @@ -257,9 +257,9 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C return; } - int x = terrainLock.Location.intX; - int y = terrainLock.Location.intY; - int z = terrainLock.Location.intZ; + double x = terrainLock.Location.X; + double y = terrainLock.Location.Y; + double z = terrainLock.Location.Z; bool err = !double.TryParse(command[2], out double scale); err = !int.TryParse(command[1], out int definition); @@ -272,9 +272,9 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C Dictionary dictionary = new Dictionary() { { "definition", definition }, - { "x", x*255 }, - { "y", y*255 }, - { "z", z*255 }, + { "x", (int)x*256 }, + { "y", (int)y*256 }, + { "z", (int)z*256 }, { "quaternionX", (double)0 }, { "quaternionY", (double)0 }, { "quaternionZ", (double)0 }, @@ -671,14 +671,12 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C int.TryParse(command[1], out int zoneid); - var zone = request.Session.ZoneMgr.GetZone((int)zoneid); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}", zone.Id)); + channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}", zoneid)); channel.SendMessageToAll(sessionManager, sender, string.Format(" AccountId CharacterId Nick Access Level Docked? DockedAt Position")); - foreach (Character c in sessionManager.SelectedCharacters) + foreach (Character c in sessionManager.SelectedCharacters.Where(x => x.ZoneId == zoneid)) { channel.SendMessageToAll(sessionManager, sender, string.Format(" {0} {1} {2} {3} {4} {5} {6}", - c.AccountId, c.Id, c.Nick, c.AccessLevel, c.IsDocked, c.GetCurrentDockingBase().Eid, c.ZonePosition)); + c.AccountId, c.Id, c.Nick, c.AccessLevel, c.IsDocked, c.GetCurrentDockingBase().Eid, c.GetPlayerRobotFromZone().CurrentPosition)); } } From 45758c637489f0623c033d1fc6a2d192c2b146ec Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Mon, 5 Feb 2018 20:28:00 -0500 Subject: [PATCH 032/457] make sure we have stronghold zones enabled. --- src/Perpetuum/Services/RiftSystem/RiftManager.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 2357f0488..c5e95b34e 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -124,11 +124,15 @@ private void SpawnRift() rift.RemovedFromZone += OnRiftRemovedFromZone; // generate a stronghold teleport and make it random chance. - int rand = r.Next(0, 10); - if (rand == 2 && !StrongHoldRiftGenerated) + // make sure we have at least one stronghold enabled. + if (_zonemanager.Zones.OfType().Count() > 0) { - rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); - rift.OriginZone = this._zone.Id; + int rand = r.Next(0, 10); + if (rand == 2 && !StrongHoldRiftGenerated) + { + rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); + rift.OriginZone = this._zone.Id; + } } var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); From 50a3301eaa2fd11bac5cf912ddd9d0dc84c0ca61 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Tue, 6 Feb 2018 00:16:36 -0500 Subject: [PATCH 033/457] properly query and cache insurance records. --- .../Services/Insurance/InsuranceHelper.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) 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; From 844ecb228bff700a65d486d79f5a02996f396df0 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 10 Feb 2018 19:14:13 -0500 Subject: [PATCH 034/457] Base mission nic pay balance; beta 3->2x nic; participant bonus 2.5% nic per (#23) --- .../MissionTargets/MissionTargetRewardCalculator.cs | 3 ++- .../MissionEngine/Missions/MissionInProgress.cs | 11 ++++++++++- .../Services/MissionEngine/Missions/MissionVisitor.cs | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index 5622a3e37..bea47a03e 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -67,7 +67,8 @@ private void PayAsMineral() } - public static readonly double[] PayOutMultipliers = new double[] { 25, 50, 100, 250, 500, 750, 1000, 1500, 2500, 4000 }; + //Changed for Economy! + public static readonly double[] PayOutMultipliers = new double[] { 50, 75, 125, 250, 450, 600, 750, 1000, 1500, 2000 }; private void PayReward() { diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index 0823ddadc..a5b049025 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1444,6 +1444,15 @@ public void PayOutMission(double rewardFraction, List participants, L double zoneFactor; GetFinalReward(out rewardSum, out distanceReward, out difficultyReward, out rewardByTargets, out riskCompensation, out zoneFactor); + //TODO expose parameter in DB + //Modify total reward by participants + //Clamp participant count to [1,MaxmimalGangParticipants] + double participantBonus = 0.025; + int participantCount = Math.Min(MaxmimalGangParticipants, Math.Max(1, participants.Count)); + double participantModifier = 1 + participantCount * participantBonus; + rewardSum *= participantModifier; + //--End participantbonus multiplier--// + paymentData.Add("totalReward", Math.Round(rewardSum)); paymentData.Add("distanceReward", distanceReward); paymentData.Add("difficultyReward", difficultyReward); @@ -2274,7 +2283,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 32a518cfe..74ed0d681 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs @@ -68,7 +68,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! } From b05c7835954b15f45752fa787a6a282b30d82562 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 10 Feb 2018 22:50:36 -0500 Subject: [PATCH 035/457] Loyal outpost bonus issue (#24) Fix for outpost facility point allocations --- .../Facilities/ProductionFacility.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs index f9d93b114..4d0dd3e6d 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,13 +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(); + modifier = Outpost.GetFacilityLevelFromStack(facility.Eid) * 25; + } + return modifier; + } + public virtual Dictionary GetFacilityInfo(Character character) { var infoData = BaseInfoToDictionary(); @@ -137,7 +150,7 @@ private bool LoadIsOnTrainingZone() protected virtual int GetFacilityPoint() { - return ED.Options.Points; + return ED.Options.Points + GetFacilityBonus(); } protected int GetPercentageFromAdditiveComponent(int additiveComponent) From ed32b8db1e80a0b16b134e65a043a2762e52280e Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 10 Feb 2018 23:51:11 -0500 Subject: [PATCH 036/457] Potential fix for Interzone Teleporters (#25) --- src/Perpetuum/Zones/Teleporting/TeleportWorldTargetHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); From 0ad7a10453118afc98387dfec73682064567bfc1 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 13 Feb 2018 22:39:48 -0500 Subject: [PATCH 037/457] rift exit temp fix for stronghold1 --- .../Services/RiftSystem/RiftManager.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index c5e95b34e..4b6fd3304 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -68,7 +68,7 @@ public class RiftManager private readonly LinkedList _nextRiftSpawns = new LinkedList(); - public RiftManager(IZone zone,TimeRange spawnTime,RiftSpawnPositionFinder spawnPositionFinder,IEntityServices entityServices, ZoneManager zoneManager) + public RiftManager(IZone zone, TimeRange spawnTime, RiftSpawnPositionFinder spawnPositionFinder, IEntityServices entityServices, ZoneManager zoneManager) { _zone = zone; _spawnTime = spawnTime; @@ -87,7 +87,14 @@ public void Update(TimeSpan time) _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); } - + + //If exit rift dies on stronghold, respawn it + if (_riftCounts < 1 && _zone is StrongHoldZone) + { + _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); + Interlocked.Increment(ref _riftCounts); + } + _nextRiftSpawns.RemoveAll(t => { t.Update(time); @@ -136,6 +143,13 @@ private void SpawnRift() } var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); + + if (_zone is StrongHoldZone) + { + //TODO: Fixme for I should be a DB table for valid spawn locations on strongholds + spawnPosition = new Position(1120, 1039); + } + rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2}) Stronghold Zone ID: {3}", _zone.Id, rift.ED.Name, rift.CurrentPosition, rift.DestinationStrongholdZone)); } From 204cd0374e581f10a3a0b4ee2966eb0f1320cad4 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Fri, 16 Feb 2018 23:32:39 -0500 Subject: [PATCH 038/457] Price calculator probably should not be a single instance object. (#27) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 1e3e77e81..9bf9db809 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -559,7 +559,7 @@ private void InitContainer(string gameRoot) _builder.RegisterType().As().SingleInstance().OnActivated(e => e.Instance.Init()); _builder.RegisterType().SingleInstance(); - _builder.RegisterType().SingleInstance(); + _builder.RegisterType(); // this doesn't appear to be something that should be a singleton. _builder.RegisterType().As().SingleInstance(); From 477a80c2aac6306a6d24a9ba06e93ec9475a4eab Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 17 Feb 2018 10:47:08 -0500 Subject: [PATCH 039/457] Changes to startup sequence to ensure markets are loaded properly after zones are created. --- .../PerpetuumBootstrapper.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 9bf9db809..030c8f56a 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -288,7 +288,7 @@ public void Init(string gameRoot) CorporationData.CorporationManager = _container.Resolve(); Character.CharacterFactory = _container.Resolve(); - Character.CharacterCache = _container.Resolve>().Invoke("CharacterCache"); + Character.CharacterCache = _container.Resolve>().Invoke("CharacterCache"); MissionHelper.Init(_container.Resolve(), _container.Resolve()); MissionHelper.MissionProcessor = _container.Resolve(); @@ -354,6 +354,12 @@ public void Init(string gameRoot) 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) @@ -1516,12 +1522,9 @@ private void RegisterMissions() } 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(); From 251a20b0c0f23d5628ad47cb577a07df4c100f5b Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Mon, 19 Feb 2018 19:40:31 -0500 Subject: [PATCH 040/457] TransactionScope not completed when settings corp-corp standings (#31) --- src/Perpetuum.RequestHandlers/Standings/SetStanding.cs | 2 -- 1 file changed, 2 deletions(-) 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) From 775aa8a790624ffedd3f04f750fb57e108cfad8e Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 1 Mar 2018 20:31:00 -0500 Subject: [PATCH 041/457] Production costing--WIP (#32) * production costs on bots working, tech levels working * add docs and notes * decrease owner facility cost recovery --- .../ProductionEngine/Facilities/Mill.cs | 63 +++++++++-------- .../Facilities/ProductionFacility.cs | 8 +++ .../ProductionEngine/Facilities/Prototyper.cs | 6 +- .../Facilities/ResearchLab.cs | 6 +- .../ProductionEngine/PBSFacilities.cs | 2 +- .../ProductionDataAccessExtensions.cs | 69 ++++++++++++++++--- .../ProductionEngine/ProductionProcessor.cs | 25 +++---- src/Perpetuum/Zones/Intrusion/Outpost.cs | 20 +++--- 8 files changed, 128 insertions(+), 71 deletions(-) diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs index 87516bc45..be60dc61f 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) @@ -94,7 +94,7 @@ private int GetAdditiveComponentForMaterial(Character character) var standingPoints = GetStandingOfOwnerToCharacter(character) * 20; var facilityPoints = GetFacilityPoint(); - return (int) (facilityPoints + standingPoints + extensionPoint); + return (int)(facilityPoints + standingPoints + extensionPoint); } @@ -104,7 +104,7 @@ private int GetAdditiveComponentForTime(Character character) var standingComponent = GetStandingOfOwnerToCharacter(character) * 20; 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 4d0dd3e6d..6f24a7129 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs @@ -222,6 +222,14 @@ private int GetStandingPoints(Character character) return (int)( GetStandingOfOwnerToCharacter(character)*20); } + //Overload method for price/sec + public virtual double GetPricePerSecond(int targetDefinition) + { + var mod = Math.Max(1, ProductionDataAccess.GetProductionPriceModifier(targetDefinition)); + mod = Math.Min(10, mod); + return GetPricePerSecond() * mod; + } + public virtual double GetPricePerSecond() { var pricePerSecond = ED.Options.PerSecondPrice; diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs index 7798ad241..1874c70d8 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs @@ -116,7 +116,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,9 +160,9 @@ 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); } diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs index 0fc1767ed..d9b9c3da0 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs @@ -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/PBSFacilities.cs b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs index 03a14e151..c39ad1c94 100644 --- a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs +++ b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs @@ -699,7 +699,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; diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs index 82c32d2bc..1c7d13f44 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs @@ -2,30 +2,33 @@ using Perpetuum.EntityFramework; using Perpetuum.Log; using Perpetuum.Services.ProductionEngine.CalibrationPrograms; +using Perpetuum.ExportedTypes; +using System; +using System.Collections.Generic; 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 +39,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 +54,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 +74,58 @@ 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) + + //TODO fixme: this is bad + private static readonly double[] TechLevelMods = { 1, 2, 3, 4 }; + private static readonly double[] BotClassMods = { 1, 2, 3, 4 }; + //This is worse + private static IEnumerable lights = new List() { CategoryFlags.cf_thelodica_runners, CategoryFlags.cf_nuimqol_runners, CategoryFlags.cf_pelistal_runners, CategoryFlags.cf_syndicate_runners, CategoryFlags.cf_industrial_runners}; + private static IEnumerable assaults = new List() { CategoryFlags.cf_thelodica_crawlers, CategoryFlags.cf_nuimqol_crawlers, CategoryFlags.cf_pelistal_crawlers, CategoryFlags.cf_syndicate_crawlers, CategoryFlags.cf_industrial_crawlers}; + private static IEnumerable mechs = new List() { CategoryFlags.cf_thelodica_mechs, CategoryFlags.cf_nuimqol_mechs, CategoryFlags.cf_pelistal_mechs, CategoryFlags.cf_syndicate_mechs, CategoryFlags.cf_industrial_mechs}; + private static IEnumerable heavies = new List() { CategoryFlags.cf_thelodica_heavymechs, CategoryFlags.cf_nuimqol_heavymechs, CategoryFlags.cf_pelistal_heavymechs, CategoryFlags.cf_syndicate_heavymechs, CategoryFlags.cf_industrial_heavymechs, CategoryFlags.cf_industrial_heavy_gliders}; + //Compute price modifier based on category of entity + public static double GetProductionPriceModifier(this IProductionDataAccess dataAccess, int targetDefinition) + { + if (!EntityDefault.TryGet(targetDefinition, out EntityDefault ed)) + { + Logger.Error("definition was not found: " + targetDefinition); + return 1.0; + } + + var modifier = 1.0; + + if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, lights)) + { + modifier = BotClassMods[0]; + } + else if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, assaults)) + { + modifier = BotClassMods[1]; + } + else if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, mechs)) + { + modifier = BotClassMods[2]; + } + else if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, heavies)) + { + modifier = BotClassMods[3]; + } + else + { + var techLevel = Math.Min(Math.Max(1, ed.Tier.level) - 1, TechLevelMods.Length - 1); + modifier = TechLevelMods[techLevel]; + } + + 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); + 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/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/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 412036472..32b30574f 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -434,16 +434,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) From d550841dbeb3a48339fa9ce9f490a3181604bfb8 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 1 Mar 2018 20:31:27 -0500 Subject: [PATCH 042/457] standings balance (#33) --- .../MissionEngine/Missions/MissionVisitor.cs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs index 74ed0d681..8eb4264fb 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs @@ -154,31 +154,16 @@ public override void VisitRandomMission(RandomMission randomMission) // 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); - - //TODO: Fixme: I am a hack for Syndicatification - if (this._missionLocation.zoneId == 0 || this._missionLocation.zoneId == 8) //For New Virginia and Hershfield + //Implementation: Add reputation for all factions equally -- No factional bias! + IEnumerable allianceEids = DefaultCorporationDataCache.GetMegaCorporationEids(); + List standingChangeForSyndicatification = new List(); + foreach(long id in allianceEids) { - //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(); - }//--end hack-- - else - { - _standingChanges = new[] { new MissionStandingChange(positiveAllianceEid, standingValue), new MissionStandingChange(opposingAllianceEid, negativeValue) }; + standingChangeForSyndicatification.Add(new MissionStandingChange(id, standingValue)); } + _standingChanges = standingChangeForSyndicatification.ToArray(); + } } From 14fa322db63b4607761069d63ab0c457d7497e43 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Thu, 1 Mar 2018 20:31:36 -0500 Subject: [PATCH 043/457] Mitigate account creation spam from the client. (#34) --- .../AdminTools/AccountOpenCreate.cs | 9 +++++++++ src/Perpetuum/Services/Sessions/Session.cs | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs index 3704166ef..5f82e09d8 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs @@ -26,6 +26,12 @@ 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.AccountAlreadyExists); + } + var account = new Account { Email = email, @@ -42,6 +48,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; + Db.Query().CommandText("extensionPointsInject") .SetParameter("@accountID", account.Id) .SetParameter("@points", 40000) diff --git a/src/Perpetuum/Services/Sessions/Session.cs b/src/Perpetuum/Services/Sessions/Session.cs index 913dfc95d..142a2bee3 100644 --- a/src/Perpetuum/Services/Sessions/Session.cs +++ b/src/Perpetuum/Services/Sessions/Session.cs @@ -28,6 +28,7 @@ public interface ISession Character Character { get; } AccessLevel AccessLevel { get; } IZoneManager ZoneMgr { get; } + bool AccountCreatedInSession { get; set; } void SendMessage(MessageBuilder builder); void SendMessage(IMessage message); @@ -132,6 +133,11 @@ public IZoneManager ZoneMgr 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) { From 2529f90e4200ef74930351ee807143d4c97721fe Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 3 Mar 2018 12:00:33 -0500 Subject: [PATCH 044/457] closes #72 and #71, re-apply invul, reduce arkhe respawn on player death (#35) --- .../Zone/Containers/EquipModule.cs | 7 +++++++ .../Zone/Containers/RemoveModule.cs | 5 +++++ .../Zone/Containers/ZoneChangeModule.cs | 1 + src/Perpetuum/Players/Player.cs | 4 ++-- src/Perpetuum/Zones/ZoneSession.cs | 1 + 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs index db1769ed8..a4b23e1d0 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs @@ -25,6 +25,8 @@ public override void HandleRequest(IZoneRequest request) { var character = request.Session.Character; var player = request.Zone.GetPlayerOrThrow(character); + //Store ratios before transaction + var hpRatio = player.ArmorPercentage; CheckPvpState(player).ThrowIfError(); CheckCombatState(player).ThrowIfError(); @@ -35,6 +37,7 @@ public override void HandleRequest(IZoneRequest request) CheckContainerType(container).ThrowIfError(); CheckFieldTerminalRange(player, container).ThrowIfError(); + player.EnlistTransaction(); container.EnlistTransaction(); @@ -59,11 +62,15 @@ public override void HandleRequest(IZoneRequest request) var moduleEid = request.Data.GetOrDefault(k.moduleEID); var module = (Module)container.GetItemOrThrow(moduleEid).Unstack(1); + module.CheckEnablerExtensionsAndThrowIfFailed(player.Character); component.EquipModuleOrThrow(module, slot); player.Initialize(character); player.CheckEnergySystemAndThrowIfFailed(); + //Apply original armor ratio after equip-action + player.Armor = player.ArmorMax * hpRatio; + player.Save(); container.Save(); diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs index e867cc7bf..50150c71f 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs @@ -15,6 +15,8 @@ public override void HandleRequest(IZoneRequest request) { var character = request.Session.Character; var player = request.Zone.GetPlayerOrThrow(character); + //Store ratios before transaction + var hpRatio = player.ArmorPercentage; CheckPvpState(player).ThrowIfError(); CheckCombatState(player).ThrowIfError(); @@ -51,6 +53,9 @@ public override void HandleRequest(IZoneRequest request) player.Initialize(character); player.CheckEnergySystemAndThrowIfFailed(); + //Apply original armor ratio after unequip-action + player.Armor = player.ArmorMax * hpRatio; + player.Save(); container.Save(); diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index e43d7f7b3..ed98edce9 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -30,6 +30,7 @@ public override void HandleRequest(IZoneRequest request) component.ChangeModuleOrThrow(sourceSlot, targetSlot); player.Initialize(character); + player.Save(); Transaction.Current.OnCommited(() => diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 203bcd4a8..24a483ac5 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -436,8 +436,8 @@ private Task HandlePlayerDeadAsync(IZone zone, Unit killer) return Task.Run(() => HandlePlayerDead(zone, killer)); } - public const int ARKHE_REQUEST_TIMER_MINUTES_PVP = 10; - public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 5; + public const int ARKHE_REQUEST_TIMER_MINUTES_PVP = 3; + public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 1; //ennek mindenkepp vegig kell futnia diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index 302bcc247..810e2b618 100644 --- a/src/Perpetuum/Zones/ZoneSession.cs +++ b/src/Perpetuum/Zones/ZoneSession.cs @@ -279,6 +279,7 @@ private void HandleAuth(Packet packet) player.SetSession(this); player.SendInitSelf(); player.ApplyTeleportSicknessEffect(); + player.ApplyInvulnerableEffect(); _player = player; } From 94b074126ef6d0038a3fffc40561e42ea270097e Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 4 Mar 2018 19:26:58 -0500 Subject: [PATCH 045/457] Fieldterm equip fix (#36) * closes #72 and #71, re-apply invul, reduce arkhe respawn on player death * closes #71 and handles accum state * Before refresh --- .../Zone/Containers/EquipModule.cs | 9 ++++++--- .../Zone/Containers/RemoveModule.cs | 12 +++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs index a4b23e1d0..88b79c02e 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs @@ -27,6 +27,7 @@ public override void HandleRequest(IZoneRequest request) var player = request.Zone.GetPlayerOrThrow(character); //Store ratios before transaction var hpRatio = player.ArmorPercentage; + var apRatio = player.CorePercentage; CheckPvpState(player).ThrowIfError(); CheckCombatState(player).ThrowIfError(); @@ -45,6 +46,11 @@ public override void HandleRequest(IZoneRequest request) Transaction.Current.OnCompleted(completed => { player.Initialize(character); + + //Apply original ratios after unequip-action + player.Armor = player.ArmorMax * hpRatio; + player.Core = player.CoreMax * apRatio; + player.SendRefreshUnitPacket(); var result = new Dictionary @@ -68,9 +74,6 @@ public override void HandleRequest(IZoneRequest request) player.Initialize(character); player.CheckEnergySystemAndThrowIfFailed(); - //Apply original armor ratio after equip-action - player.Armor = player.ArmorMax * hpRatio; - player.Save(); container.Save(); diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs index 50150c71f..de75fd496 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs @@ -17,6 +17,7 @@ public override void HandleRequest(IZoneRequest request) var player = request.Zone.GetPlayerOrThrow(character); //Store ratios before transaction var hpRatio = player.ArmorPercentage; + var apRatio = player.CorePercentage; CheckPvpState(player).ThrowIfError(); CheckCombatState(player).ThrowIfError(); @@ -34,6 +35,11 @@ public override void HandleRequest(IZoneRequest request) Transaction.Current.OnCompleted(completed => { player.Initialize(character); + + //Apply original ratios after equip-action + player.Armor = player.ArmorMax * hpRatio; + player.Core = player.CoreMax * apRatio; + player.SendRefreshUnitPacket(); var result = new Dictionary @@ -41,7 +47,6 @@ public override void HandleRequest(IZoneRequest request) {k.robot, player.ToDictionary()}, {k.container, container.ToDictionary()} }; - Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); }); @@ -51,10 +56,7 @@ public override void HandleRequest(IZoneRequest request) module.Unequip(container); player.Initialize(character); - player.CheckEnergySystemAndThrowIfFailed(); - - //Apply original armor ratio after unequip-action - player.Armor = player.ArmorMax * hpRatio; + player.CheckEnergySystemAndThrowIfFailed(); player.Save(); container.Save(); From 683f5d118366b6a87b9523d39ea020ba3ad02b47 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 5 Mar 2018 20:14:41 -0500 Subject: [PATCH 046/457] Merge conflict resolving.. (#38) * Pre-alpha v3 (#29) * Gitignore and startup args (#1) Gitignore and args * Fix for field terminals. * fix remote market server error * gives player equal syndicate protection and molecular instability when teleporting. * Tweak syndicate protection to be 25% less than instability * Fix for field terminals. (#2) * Fix for field terminals. * fix remote market server error * gives player equal syndicate protection and molecular instability when teleporting. * Tweak syndicate protection to be 25% less than instability * test feature: alter rift system to give us a random jump to the stronghold zone. * modify rifts slightly. * Fix player trades * Give new accounts EP when created. * ep activity falloff, starting, and target level (#6) * Update LICENSE (#8) * Syndicatification of assignments done; tokens and rep (#5) * Starting nic to 500k; TODO still should move to DB (#4) * use unicoin on itemshop (#10) * Fix for #52 where production missions would not advance. (#13) * add User-Agent to avoid being 403'd by current web server (#14) * in-game command interface (#16) * rudimentary in-game command interface * more commands added. * more layer editing abilities. * bugged it! * add a windows service for the server. (#15) * Fix for shutdown cancel issue (#48) * improvements to commands / teleporting. * release build x64 (#17) * improvements to the command interface * we have to teleport someplace! * rift system + stronghold overhaul. * forgot to put back random chance. * more command updates. * Tutorial Fix -- force send to TMA (#19) * Loyal dev (#20) * improvements to commands / teleporting. * improvements to the command interface * we have to teleport someplace! * rift system + stronghold overhaul. * forgot to put back random chance. * more command updates. * return players in zone properly. * make sure we have stronghold zones enabled. * properly query and cache insurance records. * Base mission nic pay balance; beta 3->2x nic; participant bonus 2.5% nic per (#23) * Loyal outpost bonus issue (#24) Fix for outpost facility point allocations * Potential fix for Interzone Teleporters (#25) * rift exit temp fix for stronghold1 * Price calculator probably should not be a single instance object. (#27) * Changes to startup sequence to ensure markets are loaded properly after zones are created. * missed add From 8c060fcc43639359716c279ede55cd7623c8851e Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Tue, 6 Mar 2018 20:10:13 -0500 Subject: [PATCH 047/457] Extensions to the server protocol. --- .../Channels/ChannelGlobalMute.cs | 1 + .../Zone/StatsMapDrawing/ZoneDrawStatMap.cs | 146 +++++++++++++++++- src/Perpetuum/Zones/ZoneConfiguration.cs | 5 +- 3 files changed, 148 insertions(+), 4 deletions(-) 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/Zone/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 791b1f2d6..9152f4c7b 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; @@ -27,10 +28,12 @@ public partial class ZoneDrawStatMap : IRequestHandler { 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(IFileSystem fileSystem,SaveBitmapHelper saveBitmapHelper,MissionDataCache missionDataCache) { @@ -46,6 +49,7 @@ public ZoneDrawStatMap(IFileSystem fileSystem,SaveBitmapHelper saveBitmapHelper, RegisterCreator("decorblock", CreateDecorBlockingMap); RegisterCreator("electroplant", CreateElectroPlantMap); RegisterCreator("structures", CreateStructuresMap); + RegisterCreator("players", CreatePlayersMap); RegisterCreator("wall", CreateWallMap); RegisterCreator("wallpossible", CreateWallPossibleMap); RegisterCreator("wallplaces", CreateWallPlaces); @@ -72,7 +76,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) @@ -80,14 +84,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 +152,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 +319,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 +353,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 +371,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 +441,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) => diff --git a/src/Perpetuum/Zones/ZoneConfiguration.cs b/src/Perpetuum/Zones/ZoneConfiguration.cs index f850fe21c..ec27cb78a 100644 --- a/src/Perpetuum/Zones/ZoneConfiguration.cs +++ b/src/Perpetuum/Zones/ZoneConfiguration.cs @@ -61,6 +61,7 @@ public IEnumerable GetAll() MaxPlayers = 10000, MaxDockingBase = record.GetValue("maxdockingbase"), PlantAltitudeScale = record.GetValue("plantaltitudescale"), + Note = record.GetValue("note"), }; config.PlantRules = _plantRuleLoader.LoadPlantRulesWithOverrides(config.plantRuleSetId); @@ -98,6 +99,7 @@ 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 int ListenerPort { get; set; } @@ -126,7 +128,8 @@ public Dictionary ToDictionary() {k.waterLevel, WaterLevel}, {k.type, (int) Type}, {k.sparkCost, SparkCost}, - {k.maxDockingBase, MaxDockingBase} + {k.maxDockingBase, MaxDockingBase}, + {k.note, Note } }; } From 65e6f2ae06b4dae9dfc1ef952768cbc5d1ba6762 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Tue, 6 Mar 2018 22:41:30 -0500 Subject: [PATCH 048/457] more server protocol additions --- .../PerpetuumBootstrapper.cs | 1 + src/Perpetuum.RequestHandlers/ForceDock.cs | 33 +++++++++++++++++++ src/Perpetuum/Commands.cs | 10 ++++++ 3 files changed, 44 insertions(+) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 030c8f56a..20d2ed104 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1969,6 +1969,7 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.PollGet); RegisterRequestHandler(Commands.PollAnswer); RegisterRequestHandler(Commands.ForceDock); + RegisterRequestHandler(Commands.ForceDockAdmin); RegisterRequestHandler(Commands.GetItemSummary); RegisterRequestHandler(Commands.ProductionHistory); 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/Commands.cs b/src/Perpetuum/Commands.cs index b3d1809d5..368aeeb94 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -2632,6 +2632,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", From f8dee31e60153657260f7d64b8fea09cd770ee67 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Wed, 7 Mar 2018 21:23:24 -0500 Subject: [PATCH 049/457] Loyal assignment (#41) * Fix for #75 - Private Assignments - Cant accept --- .../TransportAssignments/TransportAssignmentTake.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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(); From 9bebb76fd91087299f8c7f2a03bd40aeb35d1ed5 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Thu, 8 Mar 2018 23:31:39 -0500 Subject: [PATCH 050/457] Randomize loot drops (#44) * Make loot random at the time of NPC death, not server startup. --- src/Perpetuum/Items/ItemInfo.cs | 17 ++++++++++++++++- src/Perpetuum/Services/Looting/LootGenerator.cs | 2 ++ .../Services/Looting/LootItemBuilder.cs | 2 +- src/Perpetuum/Services/Looting/LootService.cs | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Items/ItemInfo.cs b/src/Perpetuum/Items/ItemInfo.cs index f7df6a148..d3e486c03 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,17 @@ 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; + Quantity = minq; + MinQty = minq; + MaxQty = maxq; + Health = (float)EntityDefault.Health; + IsRepackaged = EntityDefault.AttributeFlags.Repackable; + } + public EntityDefault EntityDefault { get { return EntityDefault.Get(Definition); } diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 66c8d0bcf..2693bfa4a 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -20,6 +20,8 @@ public IEnumerable Generate() continue; var lootItem = LootItemBuilder.Create(info.item).SetDamaged(info.damaged).Build(); + // set quantity for this item. + lootItem.Quantity = (info.item.MinQty != info.item.MaxQty) ? FastRandom.NextInt(info.item.MinQty, info.item.MaxQty) : info.item.MinQty; yield return lootItem; } } diff --git a/src/Perpetuum/Services/Looting/LootItemBuilder.cs b/src/Perpetuum/Services/Looting/LootItemBuilder.cs index ae691af7a..bb57ab057 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,0)); } public static LootItemBuilder Create(Item item) diff --git a/src/Perpetuum/Services/Looting/LootService.cs b/src/Perpetuum/Services/Looting/LootService.cs index 1dc87eceb..5b9a0178a 100644 --- a/src/Perpetuum/Services/Looting/LootService.cs +++ b/src/Perpetuum/Services/Looting/LootService.cs @@ -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) }; From 7d22c554c089c25e528b60056fe0a9b9e0324156 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Thu, 8 Mar 2018 23:31:49 -0500 Subject: [PATCH 051/457] Add/Change news remotely. (#43) --- .../PerpetuumBootstrapper.cs | 1 + src/Perpetuum.RequestHandlers/GetNews.cs | 14 ++++++- .../Perpetuum.RequestHandlers.csproj | 1 + src/Perpetuum.RequestHandlers/UpdateNews.cs | 39 +++++++++++++++++++ src/Perpetuum/Commands.cs | 15 +++++++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/Perpetuum.RequestHandlers/UpdateNews.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 20d2ed104..29cbccfa7 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1888,6 +1888,7 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.FreshNewsCount); RegisterRequestHandler(Commands.GetNews); RegisterRequestHandler(Commands.AddNews); + RegisterRequestHandler(Commands.UpdateNews); RegisterRequestHandler(Commands.NewsCategory).SingleInstance(); RegisterRequestHandler(Commands.EpForActivityDailyLog); 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/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 42b2ab696..19eb383ee 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -205,6 +205,7 @@ + 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/Commands.cs b/src/Perpetuum/Commands.cs index 368aeeb94..dd333002b 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -942,6 +942,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", From 207b8b5295adf3a68e388624efea734f258c3db5 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 8 Mar 2018 23:32:16 -0500 Subject: [PATCH 052/457] higher levels on training - fix for balance change on robottemplate (#42) --- src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs index ef6709927..051ac8815 100644 --- a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs +++ b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs @@ -99,7 +99,7 @@ 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); } From 3b8a630e3dda9693e717c10384ec351bb3790676 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 10 Mar 2018 14:32:11 -0500 Subject: [PATCH 053/457] Facility Entity dynprop field for pt customization (#45) --- src/Perpetuum/Keywords.cs | 1 + .../ProductionEngine/Facilities/ProductionFacility.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index 9442a6e5e..2c5743a9d 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -366,6 +366,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"; diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs index 6f24a7129..ee2b661b1 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs @@ -42,7 +42,7 @@ public override string ToString() { return $"{ED.Name} {ED.Definition} {Eid}"; } - + private int GetFacilityBonus() { int modifier = 0; @@ -50,7 +50,8 @@ private int GetFacilityBonus() if (dockingbase is Outpost) { ProductionFacility facility = (dockingbase as Outpost).GetProductionFacilities().Where(x => x.Eid == this.Eid).First(); - modifier = Outpost.GetFacilityLevelFromStack(facility.Eid) * 25; + 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; } From 8569fe5a007084ebc8f8bb5a805af78a20145e2b Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sat, 10 Mar 2018 14:57:03 -0500 Subject: [PATCH 054/457] quiet down the logs when in release and console when debugging. --- .../Zones/NpcSystem/Presences/RoamingPresence.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs index 16c37c06d..0e88ea8e5 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -152,9 +153,19 @@ public void Update(TimeSpan time) private void FindNextRoamingPosition() { - _presence.Log("finding new roaming position. current: " + _presence.CurrentRoamingPosition); +#if DEBUG + if (!Debugger.IsAttached) + { + _presence.Log("finding new roaming position. current: " + _presence.CurrentRoamingPosition); + } +#endif var nextRoamingPosition = _presence.PathFinder.FindNextRoamingPosition(_presence); - _presence.Log("next roaming position: " + nextRoamingPosition + " dist:" + _presence.CurrentRoamingPosition.Distance(nextRoamingPosition)); +#if DEBUG + if (!Debugger.IsAttached) + { + _presence.Log("next roaming position: " + nextRoamingPosition + " dist:" + _presence.CurrentRoamingPosition.Distance(nextRoamingPosition)); + } +#endif _presence.CurrentRoamingPosition = nextRoamingPosition; foreach (var npc in _presence.Flocks.GetMembers()) From fc6a56cdf88342907e827c25ce17453cf5e3d8a0 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 12 Mar 2018 20:06:01 -0400 Subject: [PATCH 055/457] sap loot fix (#46) --- src/Perpetuum/Items/ItemInfo.cs | 12 +++++++++++- src/Perpetuum/Services/Looting/LootGenerator.cs | 3 +-- src/Perpetuum/Services/Looting/LootItemBuilder.cs | 2 +- src/Perpetuum/Services/Looting/LootService.cs | 4 ++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Items/ItemInfo.cs b/src/Perpetuum/Items/ItemInfo.cs index d3e486c03..d1ffa1dcb 100644 --- a/src/Perpetuum/Items/ItemInfo.cs +++ b/src/Perpetuum/Items/ItemInfo.cs @@ -29,13 +29,23 @@ public ItemInfo(int definition,int quantity = 1) : this() public ItemInfo(int definition, int minq = 1, int maxq = 1) : this() { Definition = definition; - Quantity = minq; 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/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 2693bfa4a..4adbbbbce 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -20,8 +20,7 @@ public IEnumerable Generate() continue; var lootItem = LootItemBuilder.Create(info.item).SetDamaged(info.damaged).Build(); - // set quantity for this item. - lootItem.Quantity = (info.item.MinQty != info.item.MaxQty) ? FastRandom.NextInt(info.item.MinQty, info.item.MaxQty) : info.item.MinQty; + lootItem.Quantity = lootItem.ItemInfo.randomQuantity(); //roll random on generate yield return lootItem; } } diff --git a/src/Perpetuum/Services/Looting/LootItemBuilder.cs b/src/Perpetuum/Services/Looting/LootItemBuilder.cs index bb57ab057..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,0)); + 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 5b9a0178a..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); } } From aaa1dcc162e18d6351d0154d02378e10ce511103 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sun, 18 Mar 2018 18:15:31 -0400 Subject: [PATCH 056/457] reimbursement and protocol additions. --- .../PerpetuumBootstrapper.cs | 5 ++ .../AdminTools/CharactersOnline.cs | 48 +++++++++++++ .../Channels/ChannelListAll.cs | 22 ++++++ .../CreateItemRequestHandler.cs | 3 +- src/Perpetuum.RequestHandlers/GetCommands.cs | 30 ++++++++ .../Perpetuum.RequestHandlers.csproj | 5 ++ .../ReimburseItem.cs | 68 +++++++++++++++++++ .../SignInRequestHandler.cs | 5 +- .../Zone/MovePlayer.cs | 62 +++++++++++++++++ src/Perpetuum/Commands.cs | 49 +++++++++++-- src/Perpetuum/Keywords.cs | 2 + .../Services/Channels/ChannelManager.cs | 7 +- .../Services/Channels/IChannelManager.cs | 1 + src/Perpetuum/Services/Sessions/Session.cs | 4 ++ 14 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 src/Perpetuum.RequestHandlers/AdminTools/CharactersOnline.cs create mode 100644 src/Perpetuum.RequestHandlers/Channels/ChannelListAll.cs create mode 100644 src/Perpetuum.RequestHandlers/GetCommands.cs create mode 100644 src/Perpetuum.RequestHandlers/ReimburseItem.cs create mode 100644 src/Perpetuum.RequestHandlers/Zone/MovePlayer.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 29cbccfa7..8ec3bdf66 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -627,6 +627,7 @@ private void RegisterChannelTypes() RegisterRequestHandler(Commands.ChannelCreate); RegisterRequestHandler(Commands.ChannelList); + RegisterRequestHandler(Commands.ChannelListAll); RegisterRequestHandler(Commands.ChannelMyList); RegisterRequestHandler(Commands.ChannelJoin); RegisterRequestHandler(Commands.ChannelLeave); @@ -1667,6 +1668,7 @@ private void RegisterRequestHandlers() RegisterRequestHandlerFactory(); RegisterRequestHandler(Commands.GetEnums); + RegisterRequestHandler(Commands.GetCommands); RegisterRequestHandler(Commands.GetEntityDefaults).SingleInstance(); RegisterRequestHandler(Commands.GetAggregateFields).SingleInstance(); RegisterRequestHandler(Commands.GetDefinitionConfigUnits).SingleInstance(); @@ -1708,6 +1710,8 @@ private void RegisterRequestHandlers() 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); @@ -2474,6 +2478,7 @@ private void RegisterZoneRequestHandlers() RegisterZoneRequestHandler(Commands.TeleportUse); RegisterZoneRequestHandler(Commands.TeleportQueryWorldChannels); RegisterZoneRequestHandler(Commands.JumpAnywhere); + RegisterZoneRequestHandler(Commands.MovePlayer); RegisterZoneRequestHandler(Commands.ZoneDrawStatMap); RegisterZoneRequestHandler(Commands.MissionStartFromZone); RegisterZoneRequestHandler(Commands.ItemShopBuy); 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/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/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/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/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 19eb383ee..1a9fc990f 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -59,6 +59,7 @@ + @@ -83,6 +84,7 @@ + @@ -205,6 +207,8 @@ + + @@ -526,6 +530,7 @@ + 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/SignInRequestHandler.cs b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs index 33a517df0..0df2cd23c 100644 --- a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs @@ -27,9 +27,12 @@ public void HandleRequest(IRequest request) var account = LoadAccount(request); if (account == null) throw new PerpetuumException(ErrorCodes.NoSuchUser); - + // 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) 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/Commands.cs b/src/Perpetuum/Commands.cs index dd333002b..1ca1a62e9 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -520,6 +520,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", @@ -2804,6 +2817,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", @@ -3236,6 +3255,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", @@ -4178,7 +4203,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 @@ -4511,7 +4536,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 @@ -5282,13 +5307,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) @@ -5309,6 +5340,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 @@ -5368,7 +5405,7 @@ public static Command GetCommandByText(string commandText) public static readonly Command AccountBan = new Command { Text = "accountBan", - AccessLevel = AccessLevel.toolAdmin, + AccessLevel = AccessLevel.admin, Arguments = { new Argument(k.accountID), @@ -5379,7 +5416,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/Keywords.cs b/src/Perpetuum/Keywords.cs index 2c5743a9d..c210d5b2e 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"; @@ -999,6 +1000,7 @@ 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 structureEid = "structureEid"; public const string sub = "sub"; diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index 69902afdf..128d7e23d 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -265,7 +265,7 @@ public void Talk(string channelName, Character sender, string message, IRequest m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); channel.SendMessageToAll(_sessionManager, sender, message); // this is an admin or GM command. - if (message.Substring(0, 1) == "#" && sender.AccessLevel == AccessLevel.toolAdmin) + if (message.Substring(0, 1) == "#" && sender.AccessLevel == AccessLevel.admin) { channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager, this); } @@ -360,5 +360,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/IChannelManager.cs b/src/Perpetuum/Services/Channels/IChannelManager.cs index 878933d00..36261041d 100644 --- a/src/Perpetuum/Services/Channels/IChannelManager.cs +++ b/src/Perpetuum/Services/Channels/IChannelManager.cs @@ -28,5 +28,6 @@ public interface IChannelManager IEnumerable GetChannelsByMember(Character member); IEnumerable GetPublicChannels(); + IEnumerable GetAllChannels(); } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Sessions/Session.cs b/src/Perpetuum/Services/Sessions/Session.cs index 142a2bee3..631a26d37 100644 --- a/src/Perpetuum/Services/Sessions/Session.cs +++ b/src/Perpetuum/Services/Sessions/Session.cs @@ -29,6 +29,8 @@ public interface ISession 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); @@ -93,6 +95,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; } From 81a1d05b2c79336c1e6422eb79b3feb642519faf Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 23 Mar 2018 20:39:04 -0400 Subject: [PATCH 057/457] Level 3 Taps on Beta 2 Does not show as different level on client.. but they want it so.. you know --- src/Perpetuum/Services/RiftSystem/Rift.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index f4579ed18..87990501c 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -92,11 +92,23 @@ public override void Deploy(IZone zone, Player player) var maxRiftLevel = 1; if (zone.Configuration.IsAlpha) + { maxRiftLevel = 1; + } else if (zone.Configuration.IsBeta) + { maxRiftLevel = 2; + if (zone.Configuration.Id > 8 && zone.Configuration.Id < 12) + { + maxRiftLevel = 3; //TODO fixme move to DB! + Logger.Info("LEVEL 3 RIFT ON BETA!"); + } + } else if (zone.Configuration.IsGamma) + { maxRiftLevel = 3; + } + if (ED.Tier.level > maxRiftLevel) throw new PerpetuumException(ErrorCodes.RiftLevelMismatch); From 5cb74d470d223fe724c0204d7c9a4894ae205041 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 24 Mar 2018 21:13:03 -0400 Subject: [PATCH 058/457] Check email verification on Trainingexit (#47) --- .../Zones/Teleporting/Strategies/TrainingExitStrategy.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index 4af8e3f77..48f653244 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -43,6 +43,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; From 183e1454443f8cb1aa9584b220b791a2964adcd3 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sun, 25 Mar 2018 18:15:43 -0400 Subject: [PATCH 059/457] Shut down properly from either SCM or the internal shutdown command. (#51) --- src/Perpetuum.ServerService/PerpServer.cs | 75 ++++++++++++++--------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Perpetuum.ServerService/PerpServer.cs b/src/Perpetuum.ServerService/PerpServer.cs index 2c7eb4e06..a4e7ca62e 100644 --- a/src/Perpetuum.ServerService/PerpServer.cs +++ b/src/Perpetuum.ServerService/PerpServer.cs @@ -7,6 +7,7 @@ using Autofac; using Perpetuum.Host; using System.Threading; +using Perpetuum.Log; namespace Perpetuum.ServerService { @@ -16,14 +17,22 @@ public partial class PerpServer : ServiceBase public PerpServer() { InitializeComponent(); - Bootstrapper = new PerpetuumBootstrapper(); + 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; @@ -32,50 +41,56 @@ protected override void OnStart(string[] args) { Bootstrapper.Init(gameroot); } - catch + catch (Exception ex) { - // we failed to init. no idea why. we need to log this. - // usual reasons are db connection failures because we are running as a user with no privs. + Logger.Exception(ex); return; } - // start a task for our server. - Task task = Task.Run((Action)StartServer); - + container = Bootstrapper.GetContainer(); + hostStateService = container.Resolve(); + + Task task = Task.Run((Action)StartServer); + } - protected override void OnStop() + void StartServer() { - StopServer(); + Bootstrapper.Start(); + Bootstrapper.WaitForStop(); // this blocks ! + base.Stop(); // must call or the service will hang. } - // windows requests this when shutting down. - // need to make sure this is called. - protected override void OnShutdown() + void StopServer() { - 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. + } - public void StartServer() - { - Bootstrapper.Start(); - Bootstrapper.WaitForStop(); + Thread.Sleep(10000); // wait 10 seconds for the logging stuff to flush } - private void StopServer() + // SCM stopping or server rebooting (?) + protected override void OnStop() { - Autofac.IContainer _container = Bootstrapper.GetContainer(); - - Bootstrapper.Stop(); + StopServer(); + } - // get the host container and check it's status. - // if we are running still or stopping we ask windows to not kill us. - var s = _container.Resolve(); - while (s.State == HostState.Online || s.State == HostState.Stopping) - { - RequestAdditionalTime(10000); // ask for more time. Hope we get it. - } - Thread.Sleep(10000); // wait 10 seconds for their logging process to finish. + // 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(); } + } } From 085a673238108ea58ecc5e984c1bcef29c69b663 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 26 Mar 2018 19:59:59 -0400 Subject: [PATCH 060/457] EP on first-character creation (instead of account create) (#52) --- .../AdminTools/AccountCreate.cs | 9 +++------ .../AdminTools/AccountOpenCreate.cs | 11 +++-------- .../Characters/CharacterCreate.cs | 1 + src/Perpetuum/Accounting/AccountRepository.cs | 9 +++++++++ .../Teleporting/Strategies/TrainingExitStrategy.cs | 8 ++++---- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs index 1a73f75b6..790f0b15d 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountCreate.cs @@ -1,6 +1,7 @@ using Perpetuum.Accounting; using Perpetuum.Data; using Perpetuum.Host.Requests; +using System.Data; namespace Perpetuum.RequestHandlers.AdminTools { @@ -26,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; @@ -34,11 +36,6 @@ public void HandleRequest(IRequest request) _accountRepository.Insert(account); - Db.Query().CommandText("extensionPointsInject") - .SetParameter("@accountID", account.Id) - .SetParameter("@points", 40000) - .ExecuteNonQuery(); - Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs index 5f82e09d8..53933c688 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs @@ -1,5 +1,4 @@ using Perpetuum.Accounting; -using Perpetuum.Data; using Perpetuum.Host.Requests; using Perpetuum.Services.Relay; @@ -29,7 +28,7 @@ public void HandleRequest(IRequest request) // if an account was already created using this session, reject this creation attempt. if (request.Session.AccountCreatedInSession) { - throw new PerpetuumException(ErrorCodes.AccountAlreadyExists); + throw new PerpetuumException(ErrorCodes.MaxIterationsExceeded); } var account = new Account @@ -40,7 +39,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; @@ -51,11 +51,6 @@ public void HandleRequest(IRequest request) // if we get this far, make sure we can't sit here and make accounts. request.Session.AccountCreatedInSession = true; - Db.Query().CommandText("extensionPointsInject") - .SetParameter("@accountID", account.Id) - .SetParameter("@points", 40000) - .ExecuteNonQuery(); - Message.Builder.FromRequest(request).SetData(k.account, account.ToDictionary()).Send(); } } diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs index 051ac8815..59cfe378a 100644 --- a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs +++ b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs @@ -61,6 +61,7 @@ public void HandleRequest(IRequest request) if (account.FirstCharacterDate == null) { account.FirstCharacterDate = DateTime.Now; + _accountManager.AddExtensionPoints(account, 40000); //TODO: starting EP - store in DB _accountManager.Repository.Update(account); } 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/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index 48f653244..23e3be035 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -26,7 +26,7 @@ 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) { _description = description; _trainingRewardRepository = trainingRewardRepository; @@ -45,7 +45,7 @@ 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; @@ -56,7 +56,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); @@ -123,7 +123,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; From d229f3b0c9efb5114635b967c2af091ecb1dbc04 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 31 Mar 2018 16:18:41 -0400 Subject: [PATCH 061/457] Emailcheck on register; TODO need better error code on client! (#53) --- .../AdminTools/AccountOpenCreate.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs index 53933c688..3d4e10016 100644 --- a/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs +++ b/src/Perpetuum.RequestHandlers/AdminTools/AccountOpenCreate.cs @@ -31,6 +31,17 @@ public void HandleRequest(IRequest request) 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, From 29faed24cc25e895561cb0266d6c2756568444cb Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Wed, 11 Apr 2018 20:31:48 -0400 Subject: [PATCH 062/457] GM Ability to verify email addresses. (#54) * GM/Dev confirm email address * email confirming working. --- .../PerpetuumBootstrapper.cs | 1 + .../AccountConfirmEmail.cs | 31 +++++++++++++++++++ .../Perpetuum.RequestHandlers.csproj | 1 + src/Perpetuum/Accounting/Account.cs | 14 +++++++++ src/Perpetuum/Commands.cs | 10 ++++++ src/Perpetuum/Keywords.cs | 3 +- 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/Perpetuum.RequestHandlers/AccountConfirmEmail.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 8ec3bdf66..7907372ac 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1678,6 +1678,7 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.SignInSteam); RegisterRequestHandler(Commands.SignOut); RegisterRequestHandler(Commands.SteamListAccounts); + RegisterRequestHandler(Commands.AccountConfirmEmail); RegisterRequestHandler(Commands.CharacterList); RegisterRequestHandler(Commands.CharacterCreate); RegisterRequestHandler(Commands.CharacterSelect); 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/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 1a9fc990f..3c70c50cf 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -47,6 +47,7 @@ + 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/Commands.cs b/src/Perpetuum/Commands.cs index 1ca1a62e9..24f91b377 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -5399,6 +5399,16 @@ 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 // diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index c210d5b2e..f3b2ddf36 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1188,8 +1188,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"; From c528bcd4c587635f6515c0e613ad2178b5242339 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Fri, 13 Apr 2018 19:34:04 -0400 Subject: [PATCH 063/457] Fix for the relay state service. (#55) --- src/Perpetuum.RequestHandlers/SignInRequestHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs index 0df2cd23c..0cce66979 100644 --- a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs @@ -28,6 +28,11 @@ public void HandleRequest(IRequest 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); From 17e209f8048c37d8343fe7e62c2c83fff0ade799 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Wed, 18 Apr 2018 17:45:02 -0400 Subject: [PATCH 064/457] Fix for a memory leak when doing database transactions (#56) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 11 +---------- src/Perpetuum/Data/Db.cs | 6 ++++-- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 7907372ac..08de5fb3b 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -270,7 +270,6 @@ 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}"); } @@ -548,15 +547,7 @@ private void InitContainer(string gameRoot) _builder.RegisterType(); - _builder.Register(x => - { - return new TransactionScope(TransactionScopeOption.Required, new TransactionOptions - { - IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted - }); - }); - - + _builder.RegisterType(); _builder.RegisterType().As().SingleInstance(); 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 From ca8133c6eb6e2fe6ebbb08767967912076f7b72e Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sun, 22 Apr 2018 22:16:20 -0400 Subject: [PATCH 065/457] AOE tweak; Homing AI goto random home --- src/Perpetuum/Units/Unit.cs | 7 ++++--- src/Perpetuum/Zones/NpcSystem/Npc.cs | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index b99095f26..cf971e1cf 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -484,10 +484,11 @@ protected virtual void DoExplosion() private IDamageBuilder GetExplosionDamageBuilder() { + var radius = SignatureRadius * 0.66; //Note: reduced for increased bot srf-areas var damageBuilder = DamageInfo.Builder.WithAttacker(this) - .WithOptimalRange(2) - .WithFalloff(SignatureRadius) - .WithExplosionRadius(SignatureRadius); + .WithOptimalRange(1) + .WithFalloff(radius) + .WithExplosionRadius(radius); var armorMaxValue = ArmorMax; diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 11e90bca9..5a265991e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -229,7 +229,8 @@ public HomingAI(Npc npc) : base(npc) public override void Enter() { var pathFinder = new AStarFinder(Heuristic.Manhattan, npc.IsWalkable); - pathFinder.FindPathAsync(npc.CurrentPosition, npc.HomePosition).ContinueWith(t => + var randomHome = npc.HomePosition.GetRandomPositionInRange2D(1, npc.HomeRange * 0.4); + pathFinder.FindPathAsync(npc.CurrentPosition, randomHome).ContinueWith(t => { var path = t.Result; if (path == null) @@ -237,7 +238,7 @@ public override void Enter() WriteLog("Path not found! (" + npc.CurrentPosition + " => " + npc.HomePosition + ")"); var f = new AStarFinder(Heuristic.Manhattan,(x,y) => true); - path = f.FindPath(npc.CurrentPosition, npc.HomePosition); + path = f.FindPath(npc.CurrentPosition, randomHome); if (path == null) { @@ -1012,11 +1013,11 @@ private int CalculateCombatRange() .Min(); range *= BEST_COMBAT_RANGE_MODIFIER; - + range = Math.Max(3, range); return (int) range; } - private const double BEST_COMBAT_RANGE_MODIFIER = 0.5; + private const double BEST_COMBAT_RANGE_MODIFIER = 0.9; private class BodyPullThreatHelper : IEntityVisitor,IEntityVisitor { From 7b94a137f1bb3e43e45c0a2a4870216f18b284de Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 22 Apr 2018 22:18:40 -0400 Subject: [PATCH 066/457] Alpha combat missions remove faction bias (#57) * Alpha combat missions remove faction bias --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index a5b049025..026dcc935 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; } From 994111548536f5e58fa5320ac14aa1afdce4eea8 Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Sun, 22 Apr 2018 22:20:03 -0400 Subject: [PATCH 067/457] Make sure height values are preserved on blocks with no plants. (#58) --- src/Perpetuum.RequestHandlers/Zone/ZoneKillNPlants.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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; }); From 0b24a51a717fdad27aab573f66b0af10215bb498 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 23 Apr 2018 22:53:07 -0400 Subject: [PATCH 068/457] Set Homebase on trainingExit to chosen dockingbase (#60) --- .../Zones/Teleporting/Strategies/TrainingExitStrategy.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index 23e3be035..b95f737f8 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -79,6 +79,8 @@ public void DoTeleport(Player player) 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); From 70a830b9e7e92836ef52aad21f2144b748701bb1 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 25 Apr 2018 21:29:53 -0400 Subject: [PATCH 069/457] slight range reduce on AOE dmg (#61) --- src/Perpetuum/Units/Unit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index cf971e1cf..d5e6fe181 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -484,7 +484,7 @@ protected virtual void DoExplosion() private IDamageBuilder GetExplosionDamageBuilder() { - var radius = SignatureRadius * 0.66; //Note: reduced for increased bot srf-areas + var radius = SignatureRadius * 0.5; //Note: reduced for increased bot srf-areas var damageBuilder = DamageInfo.Builder.WithAttacker(this) .WithOptimalRange(1) .WithFalloff(radius) From 1ff5d9fe5f1b4909b8fa1c8755f221b66eda779b Mon Sep 17 00:00:00 2001 From: Mark Rutherford Date: Thu, 26 Apr 2018 20:26:16 -0400 Subject: [PATCH 070/457] Fix for most dangerous agents (#62) --- src/Perpetuum/Services/HighScores/HighScoreService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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(); } From 6017140e97b4d6b5dbc636b1f2245c07326593c9 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 1 May 2018 23:31:14 -0400 Subject: [PATCH 071/457] homeposition fallback on pathing (#63) --- src/Perpetuum/Zones/NpcSystem/Npc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 5a265991e..2028fbf13 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -238,7 +238,7 @@ public override void Enter() WriteLog("Path not found! (" + npc.CurrentPosition + " => " + npc.HomePosition + ")"); var f = new AStarFinder(Heuristic.Manhattan,(x,y) => true); - path = f.FindPath(npc.CurrentPosition, randomHome); + path = f.FindPath(npc.CurrentPosition, npc.HomePosition); if (path == null) { From 44ab8d7e192172d1ad4d9c3b83836d67049776ad Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 1 Jun 2018 20:35:33 -0400 Subject: [PATCH 072/457] Probe min-dist check on deploy & Despawn timer (#65) * probe distance-check on deploy and despawn timer * remove distance constraints for now --- src/Perpetuum/Zones/DistanceConstants.cs | 3 ++- .../Zones/ProximityProbes/ProximityProbe.cs | 17 +++++++++++++++++ .../ProximityProbes/ProximityProbeDeployer.cs | 15 ++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 6937b07a5..7c7847fac 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -23,7 +23,8 @@ public class DistanceConstants 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 = 0.0; + public const double PROXIMITY_PROBE_DEPLOY_RANGE_FROM_TELEPORT = 0.0; public const double PLANT_MIN_DISTANCE_FROM_BASE = 100.0; public const double PLANT_MAX_DISTANCE_FROM_OUTPOST = 300.0; diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs index 73555d674..3398a76af 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs @@ -3,12 +3,15 @@ using System.Linq; 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; namespace Perpetuum.Zones.ProximityProbes { @@ -24,6 +27,7 @@ public abstract class ProximityProbeBase : Unit , ICharactersRegistered { private readonly CharactersRegisterHelper _charactersRegisterHelper; private IntervalTimer _probingInterval = new IntervalTimer(TimeSpan.FromSeconds(10)); + private UnitDespawnHelper _despawnHelper; protected ProximityProbeBase() { @@ -32,6 +36,17 @@ protected ProximityProbeBase() 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); + } + + public void SetDespawnTime(TimeSpan despawnTime) + { + _despawnHelper = UnitDespawnHelper.Create(this, despawnTime); + } + protected internal override void UpdatePlayerVisibility(Player player) { UpdateVisibility(player); @@ -62,6 +77,8 @@ protected override void OnUpdate(TimeSpan time) //do something OnUnitsFound(robotsNearMe); } + + _despawnHelper.Update(time, this); } protected virtual bool IsActive => true; diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs index 4fda97dcd..9c2db44d3 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs @@ -1,6 +1,8 @@ -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; @@ -26,6 +28,7 @@ protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, corporation.GetProximityProbeEids().Count().ThrowIfGreaterOrEqual(maxProbes, ErrorCodes.MaximumAmountOfProbesReached); var probe = (ProximityProbeBase)_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; @@ -37,7 +40,17 @@ protected override Unit CreateDeployableItem(IZone zone, Position spawnPosition, initialMembers.Add(player.Character); probe.InitProbe( 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 From d38ac61caf81e2de545684b0b0470a7a55354f82 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 1 Jun 2018 20:35:44 -0400 Subject: [PATCH 073/457] squad-assignments participant payout increase (#66) --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index 026dcc935..b390c6956 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1449,7 +1449,7 @@ public void PayOutMission(double rewardFraction, List participants, L //TODO expose parameter in DB //Modify total reward by participants //Clamp participant count to [1,MaxmimalGangParticipants] - double participantBonus = 0.025; + double participantBonus = 0.05; int participantCount = Math.Min(MaxmimalGangParticipants, Math.Max(1, participants.Count)); double participantModifier = 1 + participantCount * participantBonus; rewardSum *= participantModifier; From f2f64c379bd0cc0486246df7dd5ba9efc7006759 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 1 Jun 2018 20:36:02 -0400 Subject: [PATCH 074/457] Self-teleport on assignment completion reduce timer (#67) --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index b390c6956..f34fa4c47 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -2057,6 +2057,7 @@ public List CollectActiveCPRGDefinitions() } public const int MISSION_SUCCESS_TELEPORT_MINUTES = 5; + public const double MISSION_SUCCESS_TELEPORT_MINUTES_BETA = 2.5; public void EnableTeleportOnSuccess(IEnumerable characters, int zoneId) @@ -2064,8 +2065,8 @@ public void EnableTeleportOnSuccess(IEnumerable characters, int zoneI 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); + //on beta, only the owner, shorter timer + zone.GetPlayer(character)?.EnableSelfTeleport(TimeSpan.FromMinutes(MISSION_SUCCESS_TELEPORT_MINUTES_BETA),zoneId); return; } From 553b32db3be654df2ce61b8d5b114c8cf0c11ac4 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 1 Jun 2018 20:36:12 -0400 Subject: [PATCH 075/457] Beta bonus (#68) * almost working global zone aura * Bug fix on aura bonus not applying to miners; beta bonus works and stacks * remove removed file --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 ++ src/Perpetuum.ExportedTypes/EffectCategory.cs | 5 +++-- src/Perpetuum.ExportedTypes/EffectType.cs | 5 +++-- src/Perpetuum/Modules/DrillerModule.cs | 4 +++- src/Perpetuum/Players/Player.cs | 10 ++++++++++ src/Perpetuum/Zones/Effects/AuraEffect.cs | 2 +- src/Perpetuum/Zones/Zone.cs | 2 ++ 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 08de5fb3b..543874c7b 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -709,6 +709,8 @@ private void RegisterEffects() _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl2); _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl3); + _builder.RegisterType().Keyed(EffectType.effect_beta_bonus); //TODO beta bonus + _builder.Register(x => { var ctx = x.Resolve(); diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 3f0115b6e..2e97a1e57 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -51,6 +51,7 @@ public enum EffectCategory : long effcat_target_paint = 0x0000000040000000, effcat_teleport_effects = 0x0000000008000000, effcat_terrain_object_effects = 0x0000080000000000, - effcat_zero_core_drop = 0x0000000400000000 - } + effcat_zero_core_drop = 0x0000000400000000, + effcat_zone_beta = 0x0000100000000000 //TODO beta bonus category -- might hijack other category if client cant display? + } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 6e82851d0..4ca641bb3 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -101,6 +101,7 @@ public enum EffectType effect_teleport_cooldown = 40, effect_teleport_self_enabler = 102, effect_teleport_sickness = 42, - effect_thermal_resist = 13 - } + effect_thermal_resist = 13, + effect_beta_bonus = 103 //TODO beta bonus effect -- ID hardcoded from inserted DB id + } } diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index e87ed07ae..5396cf2ff 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -28,7 +28,8 @@ public DrillerModule(CategoryFlags ammoCategoryFlags,RareMaterialHandler rareMat _rareMaterialHandler = rareMaterialHandler; _materialHelper = materialHelper; _miningAmountModifier = new MiningAmountModifierProperty(this); - AddProperty(_miningAmountModifier); + AddProperty(_miningAmountModifier); + } public override void AcceptVisitor(IEntityVisitor visitor) @@ -44,6 +45,7 @@ private class MiningAmountModifierProperty : ModuleProperty public MiningAmountModifierProperty(DrillerModule module) : base(module, AggregateField.mining_amount_modifier) { _module = module; + AddEffectModifier(AggregateField.effect_mining_amount_modifier); } protected override double CalculateValue() diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 24a483ac5..528133e1b 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -1022,6 +1022,7 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) player.AddToZone(zone,validPosition,zoneEnterType); player.ApplyInvulnerableEffect(); + player.ApplyZoneEffects(zone); }); scope.Complete(); @@ -1029,6 +1030,15 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) } } + public void ApplyZoneEffects(IZone zone) + { + if (zone.Configuration.IsBeta) + { + var builder = NewEffectBuilder().SetType(EffectType.effect_beta_bonus).SetOwnerToSource(); + ApplyEffect(builder); + } + } + [CanBeNull] public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, bool applyInvulnerable) { 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/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 21188bb5c..4bf004032 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -9,6 +9,7 @@ using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers; using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; using Perpetuum.Groups.Corporations; using Perpetuum.Groups.Gangs; using Perpetuum.Log; @@ -22,6 +23,7 @@ using Perpetuum.Zones.Beams; using Perpetuum.Zones.Blobs; using Perpetuum.Zones.Decors; +using Perpetuum.Zones.Effects; using Perpetuum.Zones.Environments; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; From e47b4fea95402cf48dbffc28fc7de596913da1d4 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 3 Jun 2018 19:16:04 -0400 Subject: [PATCH 076/457] Probes: Bug fixes on despawn (#69) * bug fix on probe despawn, proper clean up on death or despawn, stability over server restarts --- src/Perpetuum/Units/UnitDespawnHelper.cs | 15 ++++++++++++--- .../Zones/ProximityProbes/ProximityProbe.cs | 11 ++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Units/UnitDespawnHelper.cs b/src/Perpetuum/Units/UnitDespawnHelper.cs index c4e7b33c2..46779613d 100644 --- a/src/Perpetuum/Units/UnitDespawnHelper.cs +++ b/src/Perpetuum/Units/UnitDespawnHelper.cs @@ -6,6 +6,7 @@ namespace Perpetuum.Units { public delegate bool UnitDespawnerCanApplyEffect(Unit unit); + public delegate void UnitDespawnStrategy(Unit unit); public class UnitDespawnHelper { @@ -19,6 +20,8 @@ private UnitDespawnHelper(TimeSpan despawnTime) public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { private get; set; } + public UnitDespawnStrategy DespawnStrategy { private get; set; } + public void Update(TimeSpan time,Unit unit) { _timer.Update(time).IsPassed(() => @@ -29,9 +32,15 @@ public void Update(TimeSpan time,Unit 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(); + } }); } diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs index 3398a76af..58b0246c6 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs @@ -45,6 +45,7 @@ public virtual void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) public void SetDespawnTime(TimeSpan despawnTime) { _despawnHelper = UnitDespawnHelper.Create(this, despawnTime); + _despawnHelper.DespawnStrategy = Kill; } protected internal override void UpdatePlayerVisibility(Player player) @@ -78,6 +79,13 @@ protected override void OnUpdate(TimeSpan time) OnUnitsFound(robotsNearMe); } + if (_despawnHelper == null) + { + var m = GetPropertyModifier(AggregateField.despawn_time); + var timespan = TimeSpan.FromMilliseconds((int)m.Value); + SetDespawnTime(timespan); + } + _despawnHelper.Update(time, this); } @@ -119,11 +127,8 @@ public virtual void OnProbeDead() { //uccso info a regisztraltaknak SendProbeDead(); - PBSRegisterHelper.ClearMembersFromSql(Eid); - Zone.UnitService.RemoveUserUnit(this); - Logger.Info("probe got deleted " + Eid); } From d12092cef77635cf761c23a6ed4a9943199c02ef Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 3 Jun 2018 19:16:11 -0400 Subject: [PATCH 077/457] Fix showing channel list on mobile teleports to characters not in squad - pvp intel exploit on interzones (#70) --- .../TeleportGetChannelList.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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(); From c746e1e86b9d4710a6ad920d61aefb1fdaa99805 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 17 Jun 2018 02:11:00 -0400 Subject: [PATCH 078/457] Paint (#64) * Consumable item, as lotteryitem category, consumed only in cargo of unpacked bot docked in terminal. * Resets decay on application * Player with applied paint drops paint as loot on death (with same loot probability) * Displays dialog indicating consumption * Decay rate increased to account for srf-area increase used in decay probability --- .../PerpetuumBootstrapper.cs | 18 ++++++ src/Perpetuum.ExportedTypes/CategoryFlags.cs | 2 +- src/Perpetuum.RequestHandlers/GiftOpen.cs | 9 +-- .../UseLotteryItem.cs | 55 ++++++++++++++++++- src/Perpetuum/Items/Paint.cs | 31 +++++++++++ src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Players/Player.cs | 16 ++++++ src/Perpetuum/Robots/Robot.Properties.cs | 2 +- 8 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 src/Perpetuum/Items/Paint.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 543874c7b..120b07992 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -998,6 +998,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity();//TODO register new entitydef _builder.Register>(x => { @@ -1024,6 +1025,19 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByDefinition(ed.Definition, parameters); } + //TODO: new for paint + void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] parameters) where T : Entity + { + //TODO: this might be expensive -- string matching all defaults + var matches = ctx.Resolve().GetAll() + .Where(i => i.CategoryFlags == cf) + .Where(i => i.Name.Contains(substr)); + foreach (var ed in matches) + { + ByDefinition(ed.Definition, parameters); + } + } + ByName(DefinitionNames.LOOT_CONTAINER_OBJECT); ByName(DefinitionNames.FIELD_CONTAINER); ByName(DefinitionNames.MISSION_CONTAINER); @@ -1191,6 +1205,10 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByCategoryFlags(CategoryFlags.cf_random_research_kits); ByCategoryFlags(CategoryFlags.cf_lottery_items); + //TODO ORDER MATTERS! Register Paints AFTER lottery will ensure Paint objects are valid subset of lottery category + //TODO entitydefaults must contain name "def_paint" and have cf_lottery_items + ByNamePatternAndFlag("def_paint", CategoryFlags.cf_lottery_items); + ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); ByCategoryFlags(CategoryFlags.cf_pbs_capsules); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index a7be71856..e2e46bdf7 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -619,5 +619,5 @@ public enum CategoryFlags : long cf_zone_item_shop = 0x0000000000000796, cf_zone_objects = 0x0000000000000092, def_pbs_dogtags = 0x0000000000000691 - } + } } 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/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index f6799a35a..cb37c6890 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -7,6 +7,7 @@ using Perpetuum.EntityFramework; using Perpetuum.Host.Requests; using Perpetuum.Items; +using Perpetuum.Robots; namespace Perpetuum.RequestHandlers { @@ -19,7 +20,22 @@ public UseLotteryItem(IEntityServices entityServices) _entityServices = entityServices; } + public void HandleRequest(IRequest request) + { + var itemEid = request.Data.GetOrDefault(k.itemEID); + var item = this._entityServices.Repository.Load(itemEid); + if (item is LotteryItem) + { + this.HandleLottery(request); + } + else if (item is Paint) //TODO this is here until we can build a good category flag.. + { + this.HandlePaint(request, itemEid); + } + } + + public void HandleLottery(IRequest request) { using (var scope = Db.CreateTransaction()) { @@ -34,7 +50,7 @@ public void HandleRequest(IRequest request) var randomItem = (Item)_entityServices.Factory.CreateWithRandomEID(randomEd); randomItem.Owner = character.Eid; - container.AddItem(randomItem,true); + container.AddItem(randomItem, true); _entityServices.Repository.Delete(lotteryItem); container.Save(); @@ -51,7 +67,7 @@ public void HandleRequest(IRequest request) Message.Builder.FromRequest(request).WithData(result).Send(); }); - + scope.Complete(); } } @@ -73,5 +89,40 @@ private static void LogRandomItemCreated(Character character, Container containe .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(); + } + } } } \ No newline at end of file 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 1aa75cab3..e4a5222e5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -214,6 +214,7 @@ + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 528133e1b..332169c25 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -520,6 +520,22 @@ private void HandlePlayerDead(IZone zone, Unit killer) } } + //Check if paint was applied + paint drop chance + if (this.ED.Config.Tint != this.Tint && LootHelper.Roll(0.5)) + { + //Paint Query + //TODO: performance => cache paints in static collection + //TODO: performance => cache if painted, and paint entitydef on undock + EntityDefault paint = EntityDefault.Reader.GetAll() + .Where(i => i.CategoryFlags == CategoryFlags.cf_lottery_items) + .Where(i => i.Config.Tint == this.Tint) + .Where(i => i.Name.Contains("paint")).First(); + if (paint != null) + { + lootItems.Add(LootItemBuilder.Create(paint.Definition).SetQuantity(1).SetDamaged(false).Build()); + } + } + if (lootItems.Count > 0) { var lootContainer = LootContainer.Create().AddLoot(lootItems).BuildAndAddToZone(zone, CurrentPosition); diff --git a/src/Perpetuum/Robots/Robot.Properties.cs b/src/Perpetuum/Robots/Robot.Properties.cs index 1ee5e95f0..6d77b06a4 100644 --- a/src/Perpetuum/Robots/Robot.Properties.cs +++ b/src/Perpetuum/Robots/Robot.Properties.cs @@ -159,7 +159,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; } } From a21dd744c8023e290d7ad78a793cf51096cde28c Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Jul 2018 12:45:04 -0400 Subject: [PATCH 079/457] command for unlocking all locked ep on provided accountid (#73) --- .../PerpetuumBootstrapper.cs | 1 + .../ExtensionFreeAllLockedEpByCommand.cs | 34 +++++++++++++++++++ .../Perpetuum.RequestHandlers.csproj | 1 + src/Perpetuum/Commands.cs | 11 ++++++ .../Services/Channels/GameAdminCommands.cs | 19 +++++++++++ 5 files changed, 66 insertions(+) create mode 100644 src/Perpetuum.RequestHandlers/Extensions/ExtensionFreeAllLockedEpByCommand.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 120b07992..7dafe866b 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1872,6 +1872,7 @@ private void RegisterRequestHandlers() 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); 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/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 3c70c50cf..900d16010 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -208,6 +208,7 @@ + diff --git a/src/Perpetuum/Commands.cs b/src/Perpetuum/Commands.cs index 24f91b377..666c87351 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -3462,6 +3462,17 @@ public static Command GetCommandByText(string commandText) } }; // + //TODO new for gameadmincommand + public static readonly Command ExtensionFreeAllLockedEpCommand = new Command + { + Text = "extensionFreeAllLockedEpByCommand", + AccessLevel = AccessLevel.admin, + Arguments = + { + new Argument(k.accountID) + } + }; + public static readonly Command FreshNewsCount = new Command { Text = "freshNewsCount", diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 34c81ca7d..68436cd05 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -744,6 +744,25 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C } + //FreeAllLockedEP for account - by request of player + if (command[0] == "#unlockallep") + { + bool err = false; + err = !int.TryParse(command[1], out int accountID); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { k.accountID, accountID } + }; + + string cmd = string.Format("{0}:relay:{1}", Commands.ExtensionFreeAllLockedEpCommand.Text, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + } + } } } From eaee4137e52053355ca3b045dba27c4a983d08f0 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Jul 2018 12:45:24 -0400 Subject: [PATCH 080/457] Allied saps increase stability (#72) * allied saps increase stability * just friendly-to-friendly for ally-sap --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 32b30574f..3bcd1d0fe 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -354,7 +354,18 @@ private void HandleTakeOver(SAP sap) if (winnerCorporation is PrivateCorporation) { - if (winnerCorporation.Eid == siteInfo.Owner) + //Compare the Owner and Winner corp's relations + var ownerEid = siteInfo.Owner ?? default(long); + var ownerAndWinnerGoodRelation = false; + + var friendlyOnly = 10; + //Compare both relations between corps: + //True IFF both corps have strictly friendly relations with eachother + ownerAndWinnerGoodRelation = _corporationManager.IsStandingMatch(winnerCorporation.Eid, ownerEid, friendlyOnly); + ownerAndWinnerGoodRelation = _corporationManager.IsStandingMatch(ownerEid, winnerCorporation.Eid, friendlyOnly) && ownerAndWinnerGoodRelation; + + //Stability increase if winner is owner OR winner is in good standing with owner + if (winnerCorporation.Eid == siteInfo.Owner || ownerAndWinnerGoodRelation) { newStability = (newStability + sap.StabilityChange).Clamp(0, 100); } From cb4f8926861109dc35352fdd08e0cf0d4bc2031f Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 22 Aug 2018 19:38:44 -0400 Subject: [PATCH 081/457] remove emailConfirmed check on tutorial exit (#82) --- .../Zones/Teleporting/Strategies/TrainingExitStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index b95f737f8..aaa764c98 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -44,7 +44,7 @@ public int TrainingRewardLevel public void DoTeleport(Player player) { //Throw if email not confirmed - player.Character.GetAccount().EmailConfirmed.ThrowIfFalse(ErrorCodes.EmailNotConfirmed); + //player.Character.GetAccount().EmailConfirmed.ThrowIfFalse(ErrorCodes.EmailNotConfirmed); player.States.Dock = true; From 90c26640372965f489bb3bed82e7671fbb1a1782 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 26 Aug 2018 14:53:00 -0400 Subject: [PATCH 082/457] double spawn bug fix for randomroamers (#81) --- src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index 2e576094a..e6a433bb3 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -101,7 +101,8 @@ public virtual void Update(TimeSpan time) public void SpawnAllMembers() { - for (var i = 0; i < Configuration.FlockMemberCount; i++) + var totalToSpawn = Configuration.FlockMemberCount - MembersCount; + for (var i = 0; i < totalToSpawn; i++) { CreateMemberInZone(); } From 2d34dc5b22be1cd0aa60331ec2bb8eebf8a15e10 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 26 Aug 2018 14:53:33 -0400 Subject: [PATCH 083/457] [Econ] missions rewards (#80) * mission payout tweak --- .../MissionTargets/MissionTargetRewardCalculator.cs | 4 ++-- .../Services/MissionEngine/Missions/RandomMission.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index bea47a03e..4dfa2435c 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -67,8 +67,8 @@ private void PayAsMineral() } - //Changed for Economy! - public static readonly double[] PayOutMultipliers = new double[] { 50, 75, 125, 250, 450, 600, 750, 1000, 1500, 2000 }; + //ECONOMY! TODO put this in DB! + public static readonly double[] PayOutMultipliers = new double[] { 50, 75, 125, 250, 450, 600, 700, 800, 900, 1000 }; private void PayReward() { 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) { /* From dabd5f08b546f5de7bf87bafb87fb66415bce20f Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 26 Aug 2018 14:53:55 -0400 Subject: [PATCH 084/457] command to support mission-testing (#79) --- .../Services/Channels/GameAdminCommands.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 68436cd05..38f3e3e89 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -563,6 +563,31 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (PBSTerraformProtected)", lockedtiles.Count)); } + //MissionTestResolve - DEBUG ONLY + if (command[0] == "#testmissions") + { + int.TryParse(command[1], out int charID); + int.TryParse(command[2], out int zoneID); + int.TryParse(command[3], out int level); + int.TryParse(command[4], out int numAttempts); + int.TryParse(command[5], out int displayFlag); + int.TryParse(command[6], 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)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Running missionresolve test {0}", dictionary.ToDebugString())); + } + #endif if (command[0] == "#giveitem") @@ -763,6 +788,9 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); } + + + } } } From 5c3093e7eebefc2769553ece26847bb0c21cc62c Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 26 Aug 2018 14:54:11 -0400 Subject: [PATCH 085/457] highscore bug fix (#78) --- src/Perpetuum.RequestHandlers/GetHighScores.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 52657b210c79c8375ea6dea51b466ec49c4ca278 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 26 Aug 2018 14:55:01 -0400 Subject: [PATCH 086/457] Squad missions: split tokens, token and rep bonus by participation bonus (#76) * Squad missions: split tokens, token and rep bonus by participation bonus * Keep bonus at 1x for solo --- .../Missions/MissionInProgress.cs | 79 ++++++++++++++----- .../MissionEngine/Missions/MissionVisitor.cs | 10 ++- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index f34fa4c47..96a4dd17e 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1351,15 +1351,44 @@ 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) + { + 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; }); - spawnedItems.Add(rewardItem); + var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.MissionRewardTake) + .SetCharacter(character) + .SetItem(rewardItem) + .SetContainer(publicContainer); + character.LogTransaction(b); + + spawnedItems.Add(rewardItem); + } } } @@ -1430,11 +1459,30 @@ public double GetDifficultyReward } } + public double GetParticipantBonusModifier() + { + return computeParticipantBonusMultiplier(this._participants.Count); + } - /// - /// Both old and new tech handled - /// - public void PayOutMission(double rewardFraction, List participants, List onlineGangMembers, Dictionary successData) + private double computeParticipantBonusMultiplier(int paricipantCount) + { + //Solo or squad of 1 and initial estimate + if (paricipantCount < 2) + { + return 1.0; + } + //Modify total reward by participants + //Clamp participant count to [1,MaxmimalGangParticipants] + double participantBonus = 0.05; //TODO expose parameter in DB + int participantCount = Math.Min(MaxmimalGangParticipants, Math.Max(1, paricipantCount)); + double participantModifier = 1 + participantCount * participantBonus; + return participantModifier; + } + + /// + /// Both old and new tech handled + /// + public void PayOutMission(double rewardFraction, List participants, List onlineGangMembers, Dictionary successData) { var paymentData = new Dictionary(); @@ -1446,14 +1494,7 @@ public void PayOutMission(double rewardFraction, List participants, L double zoneFactor; GetFinalReward(out rewardSum, out distanceReward, out difficultyReward, out rewardByTargets, out riskCompensation, out zoneFactor); - //TODO expose parameter in DB - //Modify total reward by participants - //Clamp participant count to [1,MaxmimalGangParticipants] - double participantBonus = 0.05; - int participantCount = Math.Min(MaxmimalGangParticipants, Math.Max(1, participants.Count)); - double participantModifier = 1 + participantCount * participantBonus; - rewardSum *= participantModifier; - //--End participantbonus multiplier--// + rewardSum *= GetParticipantBonusModifier(); paymentData.Add("totalReward", Math.Round(rewardSum)); paymentData.Add("distanceReward", distanceReward); diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionVisitor.cs index 8eb4264fb..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; @@ -152,6 +153,8 @@ 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% @@ -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); From 5a4cee00c567c34f29579adddf8c4aef9dd67c34 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 26 Aug 2018 14:55:28 -0400 Subject: [PATCH 087/457] 20% chance on rift to omega (#74) --- src/Perpetuum/Services/RiftSystem/RiftManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 4b6fd3304..c6fcc1bfb 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -135,7 +135,7 @@ private void SpawnRift() if (_zonemanager.Zones.OfType().Count() > 0) { int rand = r.Next(0, 10); - if (rand == 2 && !StrongHoldRiftGenerated) + if (rand < 2 && !StrongHoldRiftGenerated) { rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); rift.OriginZone = this._zone.Id; From 1dc2b720fcf742e8c66b90296fb6ee2cfef27681 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 22 Sep 2018 11:40:49 -0400 Subject: [PATCH 088/457] Ep bonus command (#77) * Concurrency is FUN! --- .../PerpetuumBootstrapper.cs | 9 ++ src/Perpetuum.RequestHandlers/EPBonusEvent.cs | 38 ++++++ .../Perpetuum.RequestHandlers.csproj | 1 + src/Perpetuum/Accounting/AccountManager.cs | 69 +++++----- .../Accounting/EpForActivityLogEvent.cs | 2 + .../EpForActivityLogEventFormatter.cs | 7 +- src/Perpetuum/Commands.cs | 14 +- src/Perpetuum/Perpetuum.csproj | 2 + .../Services/Channels/GameAdminCommands.cs | 21 ++- .../EventServices/EPBonusEventService.cs | 125 ++++++++++++++++++ src/Perpetuum/Threading/ThreadLock.cs | 73 ++++++++++ 11 files changed, 324 insertions(+), 37 deletions(-) create mode 100644 src/Perpetuum.RequestHandlers/EPBonusEvent.cs create mode 100644 src/Perpetuum/Services/EventServices/EPBonusEventService.cs create mode 100644 src/Perpetuum/Threading/ThreadLock.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 7dafe866b..d5e3efdd2 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -77,6 +77,7 @@ using Perpetuum.RequestHandlers.Zone.StatsMapDrawing; using Perpetuum.Robots; using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices; using Perpetuum.Services.ExtensionService; using Perpetuum.Services.HighScores; using Perpetuum.Services.Insurance; @@ -1624,6 +1625,12 @@ private void InitRelayManager() _builder.RegisterType(); + //TODO new EPBonusEventService + _builder.RegisterType().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1))); + }); + _builder.RegisterType().As(); _builder.RegisterType(); @@ -2129,6 +2136,8 @@ private void RegisterRequestHandlers() // Open account commands RegisterRequestHandler(Commands.AccountOpenCreate); + // Event GM Commands + RegisterRequestHandler(Commands.EPBonusSet); } private void RegisterRobotTemplates() 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/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 900d16010..2a19893f3 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -125,6 +125,7 @@ + diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index b27817c1f..6e90d304c 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -5,6 +5,7 @@ using Perpetuum.Accounting.Characters; using Perpetuum.Data; using Perpetuum.Log; +using Perpetuum.Services.EventServices; using Perpetuum.Services.ExtensionService; namespace Perpetuum.Accounting @@ -14,16 +15,19 @@ public class AccountManager : IAccountManager 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; + _epBonusEventService = epBonusEventService; } public IAccountRepository Repository { get; } @@ -112,7 +116,7 @@ public IDictionary GetEPData(Account account,Character character) var lockedEPPerAccount = GetLockedEpByAccount(account); var epCollected = GetExtensionPointsCollected(account); var boostFactor = GetExperienceBoostingFactor(epCollected,SERVER_DESIRED_EP_LEVEL); - var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor)); + var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor, GetEPMultiplier())); onePointRate = account.IsDailyEpBoosted ? onePointRate * 2 : onePointRate; var result = new Dictionary @@ -130,9 +134,13 @@ public IDictionary GetEPData(Account account,Character character) return result; } - private static double GetBoostMultiplier(double boostFactor) + private static double GetBoostMultiplier(double boostFactor, double bonusMultiplier) { - return (BOOSTMULTIPLIERMAX - 1) * boostFactor; + if (bonusMultiplier > BOOSTMULTIPLIERMAX && boostFactor <= 0) + { + return bonusMultiplier - BOOSTMULTIPLIERMAX; + } + return (bonusMultiplier - 1) * boostFactor; } private static double GetExperienceBoostingFactor(int collectedEpSum,double epLevelThreshold) @@ -350,21 +358,27 @@ public void InsertPenaltyPoint(Account account,AccountExtensionPenaltyType penal (affected == 1).ThrowIfFalse(ErrorCodes.SQLInsertError); } - public int AddExtensionPointsBoostAndLog(Account account,Character character,EpForActivityType activityType,int points) + private double GetEPMultiplier() + { + return BOOSTMULTIPLIERMAX + _epBonusEventService.GetBonus(); + } + + public int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType, int points) { if (points <= 0) return 0; + var bonusMultiplier = GetEPMultiplier(); 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, bonusMultiplier); Transaction.Current.OnCommited(() => { - LogEpForActivity(account,character,activityType,rawPoints,boostedPoints,boostFactor); + LogEpForActivity(account, character, activityType, rawPoints, boostedPoints, boostFactor, (int)bonusMultiplier); }); - AddExtensionPoints(account,boostedPoints); + AddExtensionPoints(account, boostedPoints); return boostedPoints; } @@ -381,7 +395,7 @@ public void AddExtensionPoints(Account account,int pointsToInject) }); } - public void InjectExtensionPoints(Account account,int points) + private void InjectExtensionPoints(Account account,int points) { Db.Query().CommandText("extensionPointsInject") .SetParameter("@accountID",account.Id) @@ -394,11 +408,11 @@ 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 bonusMultiplier) { // >>>>>>>>>> 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, bonusMultiplier); if (account.IsDailyEpBoosted) realEpGain *= 2; return realEpGain; @@ -411,36 +425,24 @@ private int GetBoostedExtensionPoints(Account account,int points) /// /// /// - private static int AddExtensionPointWithBoosting(int extensionPointsToAdd,int accoutEpSum,double epLevelThreshold) + private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusMultiplier) { - //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, bonusMultiplier); + 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) { var epForActivityLogEvent = new EpForActivityLogEvent(activityType) { @@ -449,10 +451,12 @@ private void LogEpForActivity(Account account,Character character,EpForActivityT RawPoints = rawPoints, Points = points, BoostFactor = boostFactor, + BoostMultiplier = bonusMultiplier, + }; _epForActivityLogger.Log(epForActivityLogEvent); - Logger.Info($"EP4Activity:{activityType} accountId:{account.Id} characterId:{character.Id} raw:{rawPoints} pts:{points} boostFactor:{Math.Round(boostFactor,4)}"); + Logger.Info($"EP4Activity:{activityType} accountId:{account.Id} characterId:{character.Id} raw:{rawPoints} pts:{points} boostFactor:{Math.Round(boostFactor,4)} bonusMultiplier:{bonusMultiplier}"); } public IEnumerable GetEpForActivityHistory(Account account,DateTime earlier,DateTime later) @@ -470,6 +474,7 @@ public IEnumerable GetEpForActivityHistory(Account accoun RawPoints = r.GetValue("rawpoints"), Points = r.GetValue("points"), BoostFactor = r.GetValue("boostfactor"), + BoostMultiplier = r.GetValue(k.multiplier), Created = r.GetValue("eventtime") }; }).ToArray(); diff --git a/src/Perpetuum/Accounting/EpForActivityLogEvent.cs b/src/Perpetuum/Accounting/EpForActivityLogEvent.cs index 44685c2af..f279c0ace 100644 --- a/src/Perpetuum/Accounting/EpForActivityLogEvent.cs +++ b/src/Perpetuum/Accounting/EpForActivityLogEvent.cs @@ -19,6 +19,7 @@ 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 DateTime Created { get; set; } public IDictionary ToDictionary() @@ -30,6 +31,7 @@ public IDictionary ToDictionary() {k.rawPoints, RawPoints}, {k.points, Points}, {k.boostFactor, BoostFactor}, + {k.multiplier, BoostMultiplier}, {k.created, Created} }; diff --git a/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs b/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs index 6617cb6f8..019339c53 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 ) VALUES ( @accountId, @characterId, @epforactivityType, @rawPoints, @points, @boostFactor, @boostMultiplier)"; query.CommandText(q) .SetParameter("@accountId",logEvent.Account.Id) @@ -15,7 +15,8 @@ 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); } } } \ No newline at end of file diff --git a/src/Perpetuum/Commands.cs b/src/Perpetuum/Commands.cs index 666c87351..8e3a25793 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -3462,7 +3462,7 @@ public static Command GetCommandByText(string commandText) } }; // - //TODO new for gameadmincommand + //GameAdmin Command public static readonly Command ExtensionFreeAllLockedEpCommand = new Command { Text = "extensionFreeAllLockedEpByCommand", @@ -3473,6 +3473,18 @@ public static Command GetCommandByText(string commandText) } }; + //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", diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index e4a5222e5..4442ca601 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -278,6 +278,7 @@ + @@ -554,6 +555,7 @@ + diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 38f3e3e89..7e2145b59 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -788,8 +788,27 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); } + //EPBonusCommands + if (command[0] == "#epbonusset") + { + bool err = false; + err = !int.TryParse(command[1], out int bonusBoost); + err = !int.TryParse(command[2], out int hours); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } - + Dictionary dictionary = new Dictionary() + { + { k.bonus, bonusBoost }, + { k.duration, hours } + }; + + string cmd = string.Format("{0}:relay:{1}", Commands.EPBonusSet.Text, GenxyConverter.Serialize(dictionary)); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + channel.SendMessageToAll(sessionManager, sender, "EP Bonus Set with command: " + dictionary.ToDebugString()); + } } } 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/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); + } + } +} From 88ce585d94c9e1657a24d2735baee194408fd724 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 29 Sep 2018 18:25:56 -0400 Subject: [PATCH 089/457] set corp for corp insurance (#84) --- .../Services/ProductionEngine/Facilities/InsuraceFacility.cs | 1 + 1 file changed, 1 insertion(+) 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; From 4b9d4affb736eb023dc41d7e214c5f249f27e0d8 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 13 Oct 2018 15:37:53 -0400 Subject: [PATCH 090/457] FIX epbonus event math and logging -- epbonus event should be added after boostfactor*multiplier! (#85) --- src/Perpetuum/Accounting/AccountManager.cs | 41 ++++++++++--------- .../Accounting/EpForActivityLogEvent.cs | 2 + .../EpForActivityLogEventFormatter.cs | 5 ++- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 6e90d304c..6d7657b3a 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -116,7 +116,7 @@ public IDictionary GetEPData(Account account,Character character) var lockedEPPerAccount = GetLockedEpByAccount(account); var epCollected = GetExtensionPointsCollected(account); var boostFactor = GetExperienceBoostingFactor(epCollected,SERVER_DESIRED_EP_LEVEL); - var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor, GetEPMultiplier())); + var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor, GetEpBonusFromEvent())); onePointRate = account.IsDailyEpBoosted ? onePointRate * 2 : onePointRate; var result = new Dictionary @@ -134,13 +134,15 @@ public IDictionary GetEPData(Account account,Character character) return result; } - private static double GetBoostMultiplier(double boostFactor, double bonusMultiplier) + /// + /// 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) { - if (bonusMultiplier > BOOSTMULTIPLIERMAX && boostFactor <= 0) - { - return bonusMultiplier - BOOSTMULTIPLIERMAX; - } - return (bonusMultiplier - 1) * boostFactor; + return ((BOOSTMULTIPLIERMAX - 1) * boostFactor) + bonusIncrease; } private static double GetExperienceBoostingFactor(int collectedEpSum,double epLevelThreshold) @@ -358,9 +360,9 @@ public void InsertPenaltyPoint(Account account,AccountExtensionPenaltyType penal (affected == 1).ThrowIfFalse(ErrorCodes.SQLInsertError); } - private double GetEPMultiplier() + private double GetEpBonusFromEvent() { - return BOOSTMULTIPLIERMAX + _epBonusEventService.GetBonus(); + return _epBonusEventService.GetBonus(); } public int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType, int points) @@ -368,14 +370,14 @@ public int AddExtensionPointsBoostAndLog(Account account, Character character, E if (points <= 0) return 0; - var bonusMultiplier = GetEPMultiplier(); + var bonusIncrease = GetEpBonusFromEvent(); var rawPoints = points; var boostFactor = GetExperienceBoostingFactor(GetExtensionPointsCollected(account), SERVER_DESIRED_EP_LEVEL); - var boostedPoints = GetBoostedExtensionPoints(account, points, bonusMultiplier); + var boostedPoints = GetBoostedExtensionPoints(account, points, bonusIncrease); Transaction.Current.OnCommited(() => { - LogEpForActivity(account, character, activityType, rawPoints, boostedPoints, boostFactor, (int)bonusMultiplier); + LogEpForActivity(account, character, activityType, rawPoints, boostedPoints, boostFactor, (int)BOOSTMULTIPLIERMAX, (int)bonusIncrease); }); AddExtensionPoints(account, boostedPoints); @@ -408,11 +410,11 @@ private void InjectExtensionPoints(Account account,int points) /// Returns the boosted points /// /// - private int GetBoostedExtensionPoints(Account account, int points, double bonusMultiplier) + private int GetBoostedExtensionPoints(Account account, int points, double bonusIncrease) { // >>>>>>>>>> ami ebbol kijon az injektolhato <<<<<<<<<<< var dailyPointsSum = GetExtensionPointsCollected(account); - var realEpGain = AddExtensionPointWithBoosting(points, dailyPointsSum, SERVER_DESIRED_EP_LEVEL, bonusMultiplier); + var realEpGain = AddExtensionPointWithBoosting(points, dailyPointsSum, SERVER_DESIRED_EP_LEVEL, bonusIncrease); if (account.IsDailyEpBoosted) realEpGain *= 2; return realEpGain; @@ -424,8 +426,9 @@ private int GetBoostedExtensionPoints(Account account, int points, double bonusM /// /// /// + /// Any multiplier increase over the BOOSTMULTIPLIERMAX /// - private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusMultiplier) + private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusIncrease) { var result = 0; var currentEp = accoutEpSum; @@ -433,7 +436,7 @@ private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int a for (var i = 0; i < extensionPointsToAdd; i++) { var expBoostingFactor = GetExperienceBoostingFactor(currentEp, epLevelThreshold); - var boostingMultiplier = GetBoostMultiplier(expBoostingFactor, bonusMultiplier); + var boostingMultiplier = GetBoostMultiplier(expBoostingFactor, bonusIncrease); var pointsToAdd = CalculateBoostedExtensionPoint(1, boostingMultiplier); result += pointsToAdd; currentEp += pointsToAdd; @@ -442,7 +445,7 @@ private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int a return Math.Max(result, extensionPointsToAdd); } - private void LogEpForActivity(Account account,Character character,EpForActivityType activityType,int rawPoints,int points,double boostFactor, int bonusMultiplier) + private void LogEpForActivity(Account account, Character character, EpForActivityType activityType, int rawPoints, int points, double boostFactor, int bonusMultiplier, int additionalMultiplier) { var epForActivityLogEvent = new EpForActivityLogEvent(activityType) { @@ -452,11 +455,11 @@ private void LogEpForActivity(Account account,Character character,EpForActivityT 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)} bonusMultiplier:{bonusMultiplier}"); + 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) diff --git a/src/Perpetuum/Accounting/EpForActivityLogEvent.cs b/src/Perpetuum/Accounting/EpForActivityLogEvent.cs index f279c0ace..9feaff923 100644 --- a/src/Perpetuum/Accounting/EpForActivityLogEvent.cs +++ b/src/Perpetuum/Accounting/EpForActivityLogEvent.cs @@ -20,6 +20,7 @@ public EpForActivityLogEvent(EpForActivityType transactionType) 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() @@ -32,6 +33,7 @@ public IDictionary ToDictionary() {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 019339c53..b24715f24 100644 --- a/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs +++ b/src/Perpetuum/Accounting/EpForActivityLogEventFormatter.cs @@ -7,7 +7,7 @@ public class EpForActivityLogger : DbLogger { protected override void BuildCommand(EpForActivityLogEvent logEvent, DbQuery query) { - const string q = @"INSERT dbo.epforactivitylog (accountid, characterid, epforactivitytype, rawpoints, points, boostfactor, multiplier ) VALUES ( @accountId, @characterId, @epforactivityType, @rawPoints, @points, @boostFactor, @boostMultiplier)"; + 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) @@ -16,7 +16,8 @@ protected override void BuildCommand(EpForActivityLogEvent logEvent, DbQuery que .SetParameter("@rawPoints", logEvent.RawPoints) .SetParameter("@points", logEvent.Points) .SetParameter("@boostFactor", logEvent.BoostFactor) - .SetParameter("@boostMultiplier", logEvent.BoostMultiplier); + .SetParameter("@boostMultiplier", logEvent.BoostMultiplier) + .SetParameter("@bonusMultiplier", logEvent.EventMultiplier); } } } \ No newline at end of file From 081c696a229214967a750877b8debfa5cb6db949 Mon Sep 17 00:00:00 2001 From: Unavailable <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 21 Oct 2018 01:05:35 -0400 Subject: [PATCH 091/457] Mk2 CT "Capsules" (#83) * CT capsules: a wrapped up CT in a capsule to allow for market-interfacing before activation. upon activation - generates target CT * support for naming schema change --- .../PerpetuumBootstrapper.cs | 4 ++ .../UseLotteryItem.cs | 39 ++++++++++++++++ .../Items/CalibrationProgramCapsule.cs | 46 +++++++++++++++++++ src/Perpetuum/Perpetuum.csproj | 1 + 4 files changed, 90 insertions(+) create mode 100644 src/Perpetuum/Items/CalibrationProgramCapsule.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index d5e3efdd2..796f1fd4e 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -847,6 +847,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity();//TODO new CT Capsule! RegisterProductionFacility(); RegisterProductionFacility(); @@ -1210,6 +1211,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] //TODO entitydefaults must contain name "def_paint" and have cf_lottery_items ByNamePatternAndFlag("def_paint", CategoryFlags.cf_lottery_items); + //TODO new CalibrationTemplateItem -- activates like paint! same category! + ByNamePatternAndFlag("_CT_capsule", CategoryFlags.cf_lottery_items); + ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); ByCategoryFlags(CategoryFlags.cf_pbs_capsules); diff --git a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index cb37c6890..b6b35173c 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -33,6 +33,45 @@ public void HandleRequest(IRequest request) { this.HandlePaint(request, itemEid); } + else if (item is CalibrationProgramCapsule) + { + this.HandleCalibrationTemplateItem(request, itemEid); + } + } + + 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(); + } } public void HandleLottery(IRequest request) 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 4442ca601..5e39e165f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -214,6 +214,7 @@ + From 403403de250f84db557a776634785a4b156ab877 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 11 Jan 2019 18:24:25 -0500 Subject: [PATCH 092/457] Fastrandom in tight loops has unstable non-random behaviour (#86) --- .../Artifacts/Generators/Loot/ArtifactLootGenerator.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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; From 4a2c4e06510d6032195073ec84f04784e357d6ab Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Fri, 11 Jan 2019 23:25:09 +0000 Subject: [PATCH 093/457] EP Consumable (#89) * Added EP Boost Item support * Apparently these weren't committed although they should have been * Fixed server error not being caught --- .../PerpetuumBootstrapper.cs | 4 ++ .../Extensions/ExtensionBuyEpBoost.cs | 8 ++- .../UseLotteryItem.cs | 66 +++++++++++++++--- src/Perpetuum/Accounting/AccountManager.cs | 67 +++++++++++++------ src/Perpetuum/Accounting/IAccountManager.cs | 4 +- .../EntityFramework/EntityDefaultOptions.cs | 5 ++ src/Perpetuum/Items/EPBoost.cs | 41 ++++++++++++ src/Perpetuum/Perpetuum.csproj | 1 + 8 files changed, 160 insertions(+), 36 deletions(-) create mode 100644 src/Perpetuum/Items/EPBoost.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 796f1fd4e..a45eb8d85 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1001,6 +1001,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity();//TODO register new entitydef + RegisterEntity(); _builder.Register>(x => { @@ -1214,6 +1215,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] //TODO new CalibrationTemplateItem -- activates like paint! same category! ByNamePatternAndFlag("_CT_capsule", CategoryFlags.cf_lottery_items); + // TODO new ep boost item -- activates like paint + ByNamePatternAndFlag("def_boost_ep", CategoryFlags.cf_lottery_items); + ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); ByCategoryFlags(CategoryFlags.cf_pbs_capsules); 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/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index b6b35173c..e0ec9e479 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Transactions; +using Perpetuum.Accounting; using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers.Transaction; using Perpetuum.Containers; @@ -14,31 +15,65 @@ namespace Perpetuum.RequestHandlers public class UseLotteryItem : IRequestHandler { private readonly IEntityServices _entityServices; + private readonly IAccountManager accountManager; - public UseLotteryItem(IEntityServices entityServices) + public UseLotteryItem(IEntityServices entityServices, IAccountManager accountManager) { _entityServices = entityServices; + this.accountManager = accountManager; } public void HandleRequest(IRequest request) { var itemEid = request.Data.GetOrDefault(k.itemEID); - var item = this._entityServices.Repository.Load(itemEid); + var item = _entityServices.Repository.Load(itemEid); if (item is LotteryItem) - { - this.HandleLottery(request); - } + 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.. - { - this.HandlePaint(request, itemEid); - } + HandlePaint(request, itemEid); else if (item is CalibrationProgramCapsule) - { - this.HandleCalibrationTemplateItem(request, itemEid); - } + HandleCalibrationTemplateItem(request, itemEid); } + 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()) @@ -120,6 +155,15 @@ private static void LogOpen(Character character, Container container, LotteryIte 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) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 6d7657b3a..3d64eed6c 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.SqlClient; using System.Linq; using System.Transactions; using Perpetuum.Accounting.Characters; @@ -116,7 +117,7 @@ public IDictionary GetEPData(Account account,Character character) var lockedEPPerAccount = GetLockedEpByAccount(account); var epCollected = GetExtensionPointsCollected(account); var boostFactor = GetExperienceBoostingFactor(epCollected,SERVER_DESIRED_EP_LEVEL); - var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor, GetEpBonusFromEvent())); + var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor, GetEpBonusFromEvent(), GetEpBonusFromSubscription(account))); onePointRate = account.IsDailyEpBoosted ? onePointRate * 2 : onePointRate; var result = new Dictionary @@ -140,9 +141,9 @@ public IDictionary GetEPData(Account account,Character character) /// 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) + private static double GetBoostMultiplier(double boostFactor, double bonusIncrease, int itemIncrease) { - return ((BOOSTMULTIPLIERMAX - 1) * boostFactor) + bonusIncrease; + return ((BOOSTMULTIPLIERMAX - 1) * boostFactor) + bonusIncrease + itemIncrease; } private static double GetExperienceBoostingFactor(int collectedEpSum,double epLevelThreshold) @@ -325,22 +326,30 @@ public void AddExtensionPointsSpent(Account account,Character character,int spen /// /// Starts the subscription period /// - public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTime endTime) + public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTime endTime, int multiplierBonus) { - Db.Query().CommandText("extensionSubscriptionStart") - .SetParameter("@accountID",account.Id) - .SetParameter("@startTime",startTime) - .SetParameter("@endTime",endTime) - .ExecuteNonQuery(); + try + { + Db.Query().CommandText("extensionSubscriptionStart") + .SetParameter("@accountID", account.Id) + .SetParameter("@startTime", startTime) + .SetParameter("@endTime", endTime) + .SetParameter("@multiplierBonus", multiplierBonus) + .ExecuteNonQuery(); + } + catch (SqlException exc) + { + exc.Number.ThrowIfEqual(100000, ErrorCodes.ItemNotUsable); + } } - public void ExtensionSubscriptionExtend(Account account,DateTime extendedValidUntil) - { - Db.Query().CommandText("extensionSubscriptionExtend") - .SetParameter("@accountID",account.Id) - .SetParameter("@endTime",extendedValidUntil) - .ExecuteNonQuery(); - } + //public void ExtensionSubscriptionExtend(Account account,DateTime extendedValidUntil) + //{ + // Db.Query().CommandText("extensionSubscriptionExtend") + // .SetParameter("@accountID",account.Id) + // .SetParameter("@endTime",extendedValidUntil) + // .ExecuteNonQuery(); + //} /// /// penalty IS ++++ positive @@ -365,15 +374,31 @@ private double GetEpBonusFromEvent() return _epBonusEventService.GetBonus(); } + private int GetEpBonusFromSubscription(Account account) + { + var dataRecord = Db.Query().CommandText("getExtensionSubscription") + .SetParameter("@accountID", account.Id) + .Execute(); + + if (!dataRecord.Any()) + return 0; + + var record = dataRecord.SingleOrDefault(); + int ord = record.GetOrdinal("multiplierBonus"); + + return record.GetInt32(ord); + } + 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, bonusIncrease); + var boostedPoints = GetBoostedExtensionPoints(account, points, bonusIncrease, itemIncrease); Transaction.Current.OnCommited(() => { @@ -410,11 +435,11 @@ private void InjectExtensionPoints(Account account,int points) /// Returns the boosted points /// /// - private int GetBoostedExtensionPoints(Account account, int points, double bonusIncrease) + 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, bonusIncrease); + var realEpGain = AddExtensionPointWithBoosting(points, dailyPointsSum, SERVER_DESIRED_EP_LEVEL, bonusIncrease, itemIncrease); if (account.IsDailyEpBoosted) realEpGain *= 2; return realEpGain; @@ -428,7 +453,7 @@ private int GetBoostedExtensionPoints(Account account, int points, double bonusI /// /// Any multiplier increase over the BOOSTMULTIPLIERMAX /// - private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusIncrease) + private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusIncrease, int itemIncrease) { var result = 0; var currentEp = accoutEpSum; @@ -436,7 +461,7 @@ private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int a for (var i = 0; i < extensionPointsToAdd; i++) { var expBoostingFactor = GetExperienceBoostingFactor(currentEp, epLevelThreshold); - var boostingMultiplier = GetBoostMultiplier(expBoostingFactor, bonusIncrease); + var boostingMultiplier = GetBoostMultiplier(expBoostingFactor, bonusIncrease, itemIncrease); var pointsToAdd = CalculateBoostedExtensionPoint(1, boostingMultiplier); result += pointsToAdd; currentEp += pointsToAdd; diff --git a/src/Perpetuum/Accounting/IAccountManager.cs b/src/Perpetuum/Accounting/IAccountManager.cs index b7c041e5f..95b678f40 100644 --- a/src/Perpetuum/Accounting/IAccountManager.cs +++ b/src/Perpetuum/Accounting/IAccountManager.cs @@ -22,8 +22,8 @@ public interface IAccountManager 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 ExtensionSubscriptionExtend(Account account, DateTime extendedValidUntil); void AddExtensionPoints(Account account, int pointsToInject); int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType,int points); diff --git a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs index 24f1bf455..ec220c34e 100644 --- a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs +++ b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs @@ -17,6 +17,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); } diff --git a/src/Perpetuum/Items/EPBoost.cs b/src/Perpetuum/Items/EPBoost.cs new file mode 100644 index 000000000..2e5debe01 --- /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.UtcNow, DateTime.UtcNow.AddHours(TimePeriodHours), Boost); + } + } +} diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 5e39e165f..d78c66c2e 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -215,6 +215,7 @@ + From dbdbf16dfea604f0b00b2a421bdbcc97680d9cfe Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 21 Jan 2019 14:26:32 -0500 Subject: [PATCH 094/457] schema change (#92) --- src/Perpetuum/Accounting/AccountManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 3d64eed6c..5b04bd53f 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -330,7 +330,7 @@ public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTi { try { - Db.Query().CommandText("extensionSubscriptionStart") + Db.Query().CommandText("opp.extensionSubscriptionStart") .SetParameter("@accountID", account.Id) .SetParameter("@startTime", startTime) .SetParameter("@endTime", endTime) @@ -376,7 +376,7 @@ private double GetEpBonusFromEvent() private int GetEpBonusFromSubscription(Account account) { - var dataRecord = Db.Query().CommandText("getExtensionSubscription") + var dataRecord = Db.Query().CommandText("opp.getExtensionSubscription") .SetParameter("@accountID", account.Id) .Execute(); From 98d88341a9c65e18cafcb4869e043383e50df76a Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 28 Jan 2019 22:25:18 -0500 Subject: [PATCH 095/457] Blob-module use for NPCs and Targettable to Player targets (#88) --- .../Modules/BlobEmissionModulatorModule.cs | 24 +++++++++++++++---- src/Perpetuum/Zones/NpcSystem/Turret.cs | 19 ++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs index e564b8693..e5db6c246 100644 --- a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs +++ b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs @@ -61,10 +61,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) { @@ -88,7 +104,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/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 0d768696a..4399aee45 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -26,7 +26,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private readonly IntervalTimer _timer; private readonly ActiveModule _module; @@ -162,6 +163,22 @@ public void Visit(WebberModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } + private const double BLOBBER_CORE_THRESHOLD = 0.5; + + public void Visit(BlobEmissionModulatorModule module) + { + if (module.ParentRobot.CorePercentage < BLOBBER_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) From a9e37520817cde9494e31eaddbf988325d8b2bff Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 11 Feb 2019 18:47:47 -0500 Subject: [PATCH 096/457] Datetime fix for favoring local over utc (#93) * all server datetimes are local - consistency for now --- src/Perpetuum/Items/EPBoost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Items/EPBoost.cs b/src/Perpetuum/Items/EPBoost.cs index 2e5debe01..d95dec13a 100644 --- a/src/Perpetuum/Items/EPBoost.cs +++ b/src/Perpetuum/Items/EPBoost.cs @@ -35,7 +35,7 @@ public string Tier public void Activate(IAccountManager accountManager, Account account) { - accountManager.ExtensionSubscriptionStart(account, DateTime.UtcNow, DateTime.UtcNow.AddHours(TimePeriodHours), Boost); + accountManager.ExtensionSubscriptionStart(account, DateTime.Now, DateTime.Now.AddHours(TimePeriodHours), Boost); } } } From 276ee7d2b7f7e331b560631eadacde8c58a45f0c Mon Sep 17 00:00:00 2001 From: clouths Date: Wed, 27 Feb 2019 20:27:28 -0500 Subject: [PATCH 097/457] Omega stronghold rift now go to Hershfield main terminal (#94) * Change the exit rift of Omega Stronghold to go to Hershrfield main terminal * * Change the hardcoded position for the hershfield main terminal destination to the same logic as undocking from the station in terms of the target location of the teleport * * Replace the usage of DockingBaseHelper with a search of the Units within the zone to get the DockingBase * Use OfType method to get the docking base, since it is more convenient --- src/Perpetuum/Services/RiftSystem/Rift.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index 87990501c..0c9a2d8a0 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -9,6 +9,7 @@ using Perpetuum.Players; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Units; +using Perpetuum.Units.DockingBases; using Perpetuum.Zones; using Perpetuum.Zones.Blobs.BlobEmitters; using Perpetuum.Zones.NpcSystem.Presences; @@ -194,13 +195,13 @@ public void UseItem(Player player) // we are on a stronghold. we want to go home. - // for the moment we will send them to a teleporter on zone 0. + // Sending them at the Hershfield main terminal. if (player.Zone is StrongHoldZone) { - var destZone = player.Character.GetZone(0); - var teleportColumns = destZone.GetTeleportColumns().Where(t => t.IsEnabled); + var destZone = player.Character.GetZone(8); // Hershfield zone + var dockingBase = destZone.Units.OfType().First(); var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); - teleport.TargetPosition = teleportColumns.First().CurrentPosition; + teleport.TargetPosition = UndockSpawnPositionSelector.SelectSpawnPosition(dockingBase); teleport.DoTeleportAsync(player); return; } From 18e9a93b45593cbeb798333d5398793846c93e23 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 30 Mar 2019 01:17:06 -0400 Subject: [PATCH 098/457] epi min-dist from terminal and teleport (#96) --- src/Perpetuum/Zones/DistanceConstants.cs | 2 +- .../Generators/MineralNodeGeneratorBase.cs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 7c7847fac..80399a495 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -36,7 +36,7 @@ public class DistanceConstants public const double TERRAFORM_MIN_RANGE_FROM_OBJECTS = 25.0; public const double TERRAFORM_BUOY_RADIUS = 15; - public const double MINERAL_DISTANCE_FROM_BASE_MIN = 100; + public const double MINERAL_DISTANCE_FROM_BASE_MIN = 75; public const double EXCLUDED_TELEPORT_RANGE = 200; public const double MOBILE_TELEPORT_MIN_DISTANCE_TO_DOCKINGBASE = 50; diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs index 5e1621dff..69ad95a10 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 { @@ -72,6 +75,16 @@ protected bool IsValid(Point location) return true; } + private bool IsInRangeOfBaseOrTeleports(Point location) + { + if (_zone.Units.OfType().WithinRange(location.ToPosition(), DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN).Any()) + return true; + + if (_zone.Units.OfType().WithinRange(location.ToPosition(), DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN).Any()) + return true; + return false; + } + private MineralNode CreateMineralNode(MineralLayer layer,Dictionary tiles) { int minx = int.MaxValue, miny = int.MaxValue, maxx = 0, maxy = 0; @@ -115,6 +128,14 @@ private Position FindStartPosition(MineralLayer layer) if ( !IsValid(startPosition) ) continue; + if( layer.Configuration.Type == MaterialType.Epriton) + { + if (IsInRangeOfBaseOrTeleports(startPosition)) + { + continue; + } + } + var n = layer.GetNearestNode(startPosition); if (n == null) return startPosition; From a930e51fdd803bfebdf99d5c0fffaffae4e66316 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 6 Apr 2019 10:40:17 -0400 Subject: [PATCH 099/457] Relics (#91) * relic wip * relics wip pt2 * stop on zone process stop * locks * Relics now handled by manager, spawn beams at can location, cleanup on shutdown , almost there * tweak beam params * Relic real-implementation part 1 * better spawning logic; static relics working * relic spawn command * optimizations, docs, and cleanup, relic-despawn timer * Nullable Relicmanager nulled on zones w/o proper config, respawn with random, relicinfos have unique ep per type, activation range constant * relicinfo now supports level and faction * Lifespan of relic set to 3 days * old code cleanup * concurrent modification on beam updating - cant loop over collection in read lock only * remove unused import - old code/exp cleanup * this also needs write protection for looping on collection * use simple lock; old db scope unused * Relic inherit Unit - use despawn and zone.unit updater service to handle relic updating * refactor and cleanup for unit-supported relics * Relic despawn; proper unit add to zone * lock only on alive-param, null check on method that returns null * list relic command * refactor for bler DB changes * relic table refactor * respawn rates 0.5-2.5 hrs * Fixes and cleanups for review * missed important col * finally got whitespace fixed - to a weirdly formatted file --- .../PerpetuumBootstrapper.cs | 44 ++- .../DefinitionNames.cs | 1 + src/Perpetuum/Perpetuum.csproj | 6 + src/Perpetuum/Players/Player.cs | 4 +- .../Services/Channels/GameAdminCommands.cs | 139 +++++++- .../Services/Looting/LootContainer.cs | 4 +- src/Perpetuum/Services/Relics/Relic.cs | 166 +++++++++ src/Perpetuum/Services/Relics/RelicInfo.cs | 99 ++++++ src/Perpetuum/Services/Relics/RelicLoot.cs | 125 +++++++ src/Perpetuum/Services/Relics/RelicManager.cs | 322 ++++++++++++++++++ .../Services/Relics/RelicSpawnInfo.cs | 116 +++++++ .../Services/Relics/RelicZoneConfig.cs | 88 +++++ src/Perpetuum/Zones/IZone.cs | 2 + src/Perpetuum/Zones/Zone.cs | 9 + 14 files changed, 1113 insertions(+), 12 deletions(-) create mode 100644 src/Perpetuum/Services/Relics/Relic.cs create mode 100644 src/Perpetuum/Services/Relics/RelicInfo.cs create mode 100644 src/Perpetuum/Services/Relics/RelicLoot.cs create mode 100644 src/Perpetuum/Services/Relics/RelicManager.cs create mode 100644 src/Perpetuum/Services/Relics/RelicSpawnInfo.cs create mode 100644 src/Perpetuum/Services/Relics/RelicZoneConfig.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index a45eb8d85..3effb884a 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -98,6 +98,7 @@ 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.Sessions; using Perpetuum.Services.Social; @@ -477,6 +478,7 @@ private void InitContainer(string gameRoot) RegisterChannelTypes(); RegisterMtProducts(); RegisterRifts(); + RegisterRelics(); RegisterEffects(); RegisterIntrusions(); RegisterZones(); @@ -1002,6 +1004,8 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity();//TODO register new entitydef RegisterEntity(); + RegisterEntity(); + _builder.Register>(x => { @@ -1218,6 +1222,8 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] // TODO new ep boost item -- activates like paint ByNamePatternAndFlag("def_boost_ep", CategoryFlags.cf_lottery_items); + ByNamePatternAndFlag(DefinitionNames.RELIC, CategoryFlags.undefined); + ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); ByCategoryFlags(CategoryFlags.cf_pbs_capsules); @@ -2345,6 +2351,42 @@ private void RegisterRifts() }); } + private void RegisterRelics() + { + _builder.RegisterType(); + + _builder.Register>(x => + { + var ctx = x.Resolve(); + return zone => + { + var numRelicConfigs = Db.Query().CommandText("SELECT id FROM relicspawninfo WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute().Count; + if (numRelicConfigs < 1) + { + return null; + } + + var zoneConfigs = Db.Query().CommandText("SELECT maxspawn FROM reliczoneconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute(); + if (zoneConfigs.Count < 1) + { + return null; + } + var record = zoneConfigs[0]; + var 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(); @@ -2399,7 +2441,6 @@ void RegisterZone(ZoneType type) where T:Zone _builder.RegisterType(); _builder.RegisterType(); - _builder.Register>(x => { var ctx = x.Resolve(); @@ -2424,6 +2465,7 @@ void RegisterZone(ZoneType type) where T: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) { diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 48cf19f94..52db9a669 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4351,6 +4351,7 @@ 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 RELIC = "def_relic"; 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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d78c66c2e..f95f05601 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -288,6 +288,12 @@ + + + + + + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 332169c25..e2503fd6e 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -606,7 +606,7 @@ protected override void OnTileChanged() base.OnTileChanged(); var zone = Zone; - if (zone == null) + if (zone == null) return; MissionHandler?.MissionUpdateOnTileChange(); @@ -615,7 +615,7 @@ protected override void OnTileChanged() ApplyHighwayEffect(controlInfo.IsAnyHighway); - if (zone.Configuration.Protected) + if (zone.Configuration.Protected) return; //PVP zone diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 7e2145b59..70009162d 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -45,7 +45,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, "Channel must be secured before sending commands."); return; - } + } if (command[0] == "#shutdown") { @@ -106,16 +106,16 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - + // get the target character's session. - var charactersession = sessionManager.GetByCharacter(characterID); + var charactersession = sessionManager.GetByCharacter(characterID); if (charactersession.Character.ZoneId == null) { channel.SendMessageToAll(sessionManager, sender, string.Format("ERR: Character with ID {0} does not have a zone. Are they docked?", characterID)); return; } - + // get destination zone. var zone = request.Session.ZoneMgr.GetZone(zoneID); @@ -162,7 +162,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C { { "eid", eid } }; - + string cmd = string.Format("zoneDrawBlockingByEid:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); } @@ -609,7 +609,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Gave Item {0} ", definition)); } - + if (command[0] == "#getlockedtileproperties") { @@ -618,7 +618,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C var player = zone.GetPlayer(character.ActiveRobotEid); var lockedtile = player.GetPrimaryLock(); - + TerrainControlInfo ti = zone.Terrain.Controls.GetValue((lockedtile as TerrainLock).Location); channel.SendMessageToAll(sessionManager, sender, string.Format("Tile at {0},{1} has the following flags..", (lockedtile as TerrainLock).Location.X, (lockedtile as TerrainLock).Location.Y)); @@ -726,7 +726,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C var c = sessionManager.GetByCharacter(characterid); channelmanager.JoinChannel(channel.Name, c.Character, ChannelMemberRole.Operator, string.Empty); - + channel.SendMessageToAll(sessionManager, sender, string.Format("Added character {0} to channel ", c.Character.Nick)); } @@ -786,6 +786,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C string cmd = string.Format("{0}:relay:{1}", Commands.ExtensionFreeAllLockedEpCommand.Text, GenxyConverter.Serialize(dictionary)); request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + channel.SendMessageToAll(sessionManager, sender, "unlockallep: " + dictionary.ToDebugString()); } //EPBonusCommands @@ -810,6 +811,128 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, "EP Bonus Set with command: " + dictionary.ToDebugString()); } + +#if DEBUG + //SpawnRelics by command + if (command[0] == "#spawnrelic") + { + bool err = false; + + var character = request.Session.Character; + var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var terrainLock = player.GetPrimaryLock() as TerrainLock; + + int x, y, zoneid; + + if (terrainLock == null) + { + if (command.Length != 4) + { + channel.SendMessageToAll(sessionManager, sender, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + err = !int.TryParse(command[1], out int xCommand); + err = !int.TryParse(command[2], out int yCommand); + err = !int.TryParse(command[3], out int zoneCommand); + if (err) + { + channel.SendMessageToAll(sessionManager, sender, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + x = xCommand; + y = yCommand; + zoneid = zoneCommand; + zone = request.Session.ZoneMgr.GetZone((int)zoneid); + if (zone == null) + { + channel.SendMessageToAll(sessionManager, sender, "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) + { + channel.SendMessageToAll(sessionManager, sender, "Spawned relic at: " + dictionary.ToDebugString()); + } + else + { + channel.SendMessageToAll(sessionManager, sender, "FAILED to spawn relic at: " + dictionary.ToDebugString()); + } + } + else + { + channel.SendMessageToAll(sessionManager, sender, "This zone does NOT support relics!"); + } + + } +#endif + + //List all Relics + if (command[0] == "#listrelics") + { + bool err = false; + + var character = request.Session.Character; + IZone zone = null; + + if (command.Length == 2) + { + err = !int.TryParse(command[1], out int zoneCommand); + if (err) + { + channel.SendMessageToAll(sessionManager, sender, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + var zoneid = zoneCommand; + zone = request.Session.ZoneMgr.GetZone((int)zoneid); + } + else if (character.ZoneId != null) + { + zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); + } + + if (zone == null) + { + channel.SendMessageToAll(sessionManager, sender, "Zone not provided or not found"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + {"zoneid", zone.Id } + }; + if (zone.RelicManager != null) + { + var relicDictList = zone.RelicManager.GetRelicListDictionary(); + foreach (var dict in relicDictList) + { + channel.SendMessageToAll(sessionManager, sender, dict.ToDebugString()); + } + } + else + { + channel.SendMessageToAll(sessionManager, sender, "This zone does NOT support relics!"); + } + + } + + } } } 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/Relics/Relic.cs b/src/Perpetuum/Services/Relics/Relic.cs new file mode 100644 index 000000000..e6adc4fd8 --- /dev/null +++ b/src/Perpetuum/Services/Relics/Relic.cs @@ -0,0 +1,166 @@ +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 Relic : Unit + { + private RelicInfo _info; + private IZone _zone; + private bool _alive; + + private const double ACTIVATION_RANGE = 3; //30m + private const double RESPAWN_PROXIMITY = 10.0 * ACTIVATION_RANGE; + private readonly TimeSpan MAXLIFESPAN = TimeSpan.FromDays(3); + private TimeSpan lifespan = TimeSpan.Zero; + + // 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); + + private RelicLootItems _loots; + + [CanBeNull] + public static Relic BuildAndAddToZone(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems) + { + var relic = (Relic)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); + } + + + protected internal override void UpdatePlayerVisibility(Player player) + { + if (GetPosition().TotalDistance2D(player.CurrentPosition) < ACTIVATION_RANGE && IsAlive()) + { + PopRelic(player); + } + } + + private 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/RelicInfo.cs b/src/Perpetuum/Services/Relics/RelicInfo.cs new file mode 100644 index 000000000..d87d3cb71 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicInfo.cs @@ -0,0 +1,99 @@ +using Perpetuum.Data; +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 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(); + } + + + 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/RelicManager.cs b/src/Perpetuum/Services/Relics/RelicManager.cs new file mode 100644 index 000000000..a2bd9afbc --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicManager.cs @@ -0,0 +1,322 @@ +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Services.Looting; +using System.Drawing; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using Perpetuum.Players; +using Perpetuum.Data; +using Perpetuum.Zones.Beams; +using System.Threading; +using Perpetuum.Threading; +using Perpetuum.EntityFramework; +using Perpetuum.Units; +using Perpetuum.Items; + +namespace Perpetuum.Services.Relics +{ + public class RelicManager + { + //Constants + private const double ACTIVATION_RANGE = 3; //30m + private const double RESPAWN_PROXIMITY = 10.0 * ACTIVATION_RANGE; + private readonly TimeSpan RESPAWN_RANDOM_WINDOW = TimeSpan.FromHours(1); + private readonly TimeSpan THREAD_TIMEOUT = TimeSpan.FromSeconds(4); + + private IZone _zone; + private RiftSpawnPositionFinder _spawnPosFinder; + private ReaderWriterLockSlim _lock; + private Random _random; + + private int _max_relics = 0; + private IEnumerable _spawnInfos; + private List _relics; + + private readonly TimeSpan _respawnRate = TimeSpan.FromHours(1.5); + private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); + + //DB-accessing objects + private RelicZoneConfigRepository relicZoneConfigRepository; + private RelicSpawnInfoRepository relicSpawnInfoRepository; + private RelicLootGenerator relicLootGenerator; + private readonly IEntityServices _entityServices; + + //Timers for update + private TimeSpan _refreshElapsed; + private TimeSpan _respawnElapsed; + private TimeSpan _respawnRandomized; + + public RelicManager(IZone zone, IEntityServices entityServices) + { + _random = new Random(); + _lock = new ReaderWriterLockSlim(); + _relics = new List(); + _zone = zone; + _entityServices = entityServices; + _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(); + } + + private TimeSpan RollNextSpawnTime() + { + var randomFactor = _random.NextDouble() - 0.5; + var minutesToAdd = RESPAWN_RANDOM_WINDOW.TotalMinutes * randomFactor; + + return _respawnRate.Add(TimeSpan.FromMinutes(minutesToAdd)); + } + + private int GetRelicCount() + { + using (_lock.Read(THREAD_TIMEOUT)) + return _relics.Count; + } + + public void Start() + { + //Inject max relics on first start + while (GetRelicCount() < _max_relics) + { + SpawnRelic(); + } + } + + public void Stop() + { + //TODO cleanup if using DB to cache relics + } + + 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 _relics.Select(r => r.ToDebugDictionary()).ToList(); + } + + private 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 + } + + private 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; + } + + + 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)) + { + pt = _spawnPosFinder.FindSpawnPosition(); + attempts++; + if (attempts > maxAttempts) + { + Logger.Error("Could not get Position for next Relic on Zone: " + _zone.Id); + return; + } + } + AddRelicToZone(info, pt.ToPosition()); + } + + private void AddRelicToZone(RelicInfo info, Position position) + { + using (_lock.Write(THREAD_TIMEOUT)) + { + var r = Relic.BuildAndAddToZone(info, _zone, position, relicLootGenerator.GenerateLoot(info)); + 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 RefreshBeam(Relic 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); + } + } + + private void UpdateRelics() + { + using (_lock.Write(THREAD_TIMEOUT)) + { + foreach (Relic r in _relics) + { + if (!r.IsAlive()) + { + r.RemoveFromZone(); + } + } + _relics.RemoveAll(r => !r.IsAlive()); + } + using (_lock.Read(THREAD_TIMEOUT)) + { + foreach (Relic r in _relics) + { + RefreshBeam(r); + } + } + } + + public 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; + } + + } + } +} diff --git a/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs b/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs new file mode 100644 index 000000000..939a8ba8d --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs @@ -0,0 +1,116 @@ +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; + private IZone _zone; + + public RelicSpawnInfoRepository(IZone zone) + { + _relicSpawnInfoReader = new RelicSpawnInfoReader(zone); + _zone = 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/Zones/IZone.cs b/src/Perpetuum/Zones/IZone.cs index 1800844e7..0a1dce1bb 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -5,6 +5,7 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Log; using Perpetuum.Players; +using Perpetuum.Services.Relics; using Perpetuum.Units; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Decors; @@ -47,6 +48,7 @@ public interface IZone PBSHighwayHandler HighwayHandler { get; } TerraformHandler TerraformHandler { get; } MiningLogHandler MiningLogHandler { get; } + RelicManager RelicManager { get; } IZoneUnitService UnitService { get; } IZoneEnterQueueService EnterQueueService { get; } diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 4bf004032..ce1ab018d 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -16,6 +16,7 @@ using Perpetuum.Network; using Perpetuum.Players; using Perpetuum.Services.HighScores; +using Perpetuum.Services.Relics; using Perpetuum.Services.RiftSystem; using Perpetuum.Services.Sessions; using Perpetuum.Timers; @@ -68,6 +69,9 @@ public abstract class Zone : Threading.Process.Process,IZone [CanBeNull] public RiftManager RiftManager { private get; set; } + [CanBeNull] + public RelicManager RelicManager { get; set; } + public IZoneEnterQueueService EnterQueueService { get; set; } public IHighScoreService HighScores { get; set; } @@ -96,12 +100,16 @@ 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(); @@ -302,6 +310,7 @@ public override void Update(TimeSpan time) UpdateUnits(time); RiftManager?.Update(time); + RelicManager?.Update(time); MiningLogHandler.Update(time); MeasureUpdate(time); } From 38efc929c36c94e9d31452ea41e33e4c25940a1f Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 6 Apr 2019 13:24:55 -0400 Subject: [PATCH 100/457] NPC - Boss features (#90) Loot splitting and respawn timer random-factor strategy --- .../Looting/CompositeLootGenerator.cs | 7 ++ .../Services/Looting/ILootGenerator.cs | 7 ++ .../Services/Looting/LootGenerator.cs | 73 +++++++++++++++++++ .../Services/Looting/NullLootGenerator.cs | 5 ++ src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 1 + .../NpcSystem/Flocks/FlockConfiguration.cs | 1 + .../Flocks/FlockConfigurationRepository.cs | 1 + .../NpcSystem/Flocks/IFlockConfiguration.cs | 1 + .../Zones/NpcSystem/Flocks/NormalFlock.cs | 11 ++- src/Perpetuum/Zones/NpcSystem/Npc.cs | 27 ++++++- 10 files changed, 132 insertions(+), 2 deletions(-) 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/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 4adbbbbce..c6f92a0ed 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -1,4 +1,8 @@ +using Perpetuum.Items; +using Perpetuum.Log; +using System; using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Services.Looting { @@ -12,6 +16,11 @@ public LootGenerator(IEnumerable lootInfos) _lootInfos = lootInfos; } + public IReadOnlyCollection GetInfos() + { + return this._lootInfos.ToList(); + } + public IEnumerable Generate() { foreach (var info in _lootInfos) @@ -25,4 +34,68 @@ public IEnumerable Generate() } } } + + + public class SplittableLootGenerator : ISplittableLootGenerator + { + private readonly IReadOnlyCollection _lootInfos; + 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, splitQuantity, splitQuantity); + 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/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/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index e6a433bb3..8a81e7e33 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -114,6 +114,7 @@ protected virtual void CreateMemberInZone() { var npc = (Npc)EntityService.Factory.Create(Configuration.EntityDefault, EntityIDGenerator.Random); npc.Behavior = GetBehavior(); + npc.SpecialType = Configuration.SpecialType; var gen = new CompositeLootGenerator( new LootGenerator(LootService.GetNpcLootInfos(npc.Definition)), diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs index f3e3d0783..77c3c0a4c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs @@ -17,6 +17,7 @@ public class FlockConfiguration : IFlockConfiguration public bool IsCallForHelp { get; set; } public bool Enabled { get; set; } public NpcBehaviorType BehaviorType { get; set; } + public NpcSpecialType SpecialType { get; set; } public int HomeRange { get; set; } public EntityDefault EntityDefault { get; set; } public TimeSpan RespawnTime { get; set; } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs index ea4ad59ea..37d5afafc 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -39,6 +39,7 @@ public void LoadAllConfig() c.IsCallForHelp = r.GetValue("iscallforhelp"); c.Enabled = r.GetValue("enabled"); c.BehaviorType = (NpcBehaviorType) r.GetValue("behaviorType"); + c.SpecialType = (NpcSpecialType) r.GetValue("npcSpecialType"); }); var config = builder.Build(); _flockConfigurations[config.ID] = config; diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs index eda951e82..00114807b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs @@ -20,5 +20,6 @@ public interface IFlockConfiguration string Note { get; } bool IsCallForHelp { get; } NpcBehaviorType BehaviorType { get; } + NpcSpecialType SpecialType { get; } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index e0a3003e5..1b8518ce2 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs @@ -66,7 +66,16 @@ protected override void OnMemberDead(Unit killer,Unit npc) base.OnMemberDead(killer,npc); _allowMultiplierChange = true; - _spawnTimes.Enqueue(new TimeTracker(RespawnTime)); + _spawnTimes.Enqueue(new TimeTracker(GetRespawnTime())); + } + + private TimeSpan GetRespawnTime() + { + if (Configuration.SpecialType == NpcSpecialType.Boss) + { + return RespawnTime.Multiply(FastRandom.NextDouble(0.9, 1.15)); + } + return RespawnTime; } public override void Update(TimeSpan time) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 2028fbf13..d4ac265a0 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -38,6 +38,12 @@ public enum NpcBehaviorType Aggressive } + public enum NpcSpecialType + { + Normal, + Boss + } + public abstract class NpcAI : IState { protected readonly Npc npc; @@ -568,6 +574,7 @@ public Npc(TagHelper tagHelper) } public NpcBehavior Behavior { get; set; } + public NpcSpecialType SpecialType { get; set; } [CanBeNull] private INpcGroup _group; @@ -723,7 +730,25 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) using (var scope = Db.CreateTransaction()) { - LootContainer.Create().SetOwner(tagger).AddLoot(LootGenerator).BuildAndAddToZone(zone, CurrentPosition); + + if (SpecialType == NpcSpecialType.Boss) + { + //Boss - Split loot equally to all participants + List participants = new List(); + participants = ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)).ToList(); + 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 + { + //Normal case: loot can awarded in full to tagger + LootContainer.Create().SetOwner(tagger).AddLoot(LootGenerator).BuildAndAddToZone(zone, CurrentPosition); + } + var killerPlayer = zone.ToPlayerOrGetOwnerPlayer(killer); From a0a1fc8eb98e6df910b0e18f6d877c29a6ab8dc1 Mon Sep 17 00:00:00 2001 From: clouths Date: Sat, 6 Apr 2019 13:49:10 -0400 Subject: [PATCH 101/457] Welcome mail feature (#98) --- .../Characters/CharacterCreate.cs | 7 ++++ .../Services/Channels/GameAdminCommands.cs | 1 + src/Perpetuum/Services/Mail/MailHandler.cs | 34 +++++++++++++++++++ .../Strategies/TrainingExitStrategy.cs | 6 ++++ 4 files changed, 48 insertions(+) diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs index 59cfe378a..bd25d6e8e 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,8 @@ namespace Perpetuum.RequestHandlers.Characters { public class CharacterCreate : IRequestHandler { + private TimeSpan WAIT_TIME_BEFORE_SENDING_MAIL = TimeSpan.FromSeconds(10); + private readonly IAccountManager _accountManager; private readonly IChannelManager _channelManager; private readonly DockingBaseHelper _dockingBaseHelper; @@ -103,6 +107,9 @@ public void HandleRequest(IRequest request) character.SetAllExtensionLevel(6); dockingBase.CreateStarterRobotForCharacter(character); character.AddToWallet(TransactionType.CharacterCreate,10000000); + + Task.Delay(WAIT_TIME_BEFORE_SENDING_MAIL) + .ContinueWith(task => MailHandler.SendWelcomeMailBeginTutorial(character)); } character.CurrentDockingBaseEid = dockingBase.Eid; diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 70009162d..008354c59 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -3,6 +3,7 @@ using Perpetuum.GenXY; using Perpetuum.Host.Requests; using Perpetuum.Players; +using Perpetuum.Services.Mail; using Perpetuum.Services.RiftSystem; using Perpetuum.Services.Sessions; using Perpetuum.Zones; diff --git a/src/Perpetuum/Services/Mail/MailHandler.cs b/src/Perpetuum/Services/Mail/MailHandler.cs index debc94828..f21fb90b4 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, "TutorialArriveMail"); + } + + public static ErrorCodes SendWelcomeMailExitTutorial(Character newPlayer) + { + return SendWelcomeMail(newPlayer, "TutorialCompleteMail"); + } + + 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/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index aaa764c98..e92549040 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; @@ -16,6 +18,7 @@ public class TrainingExitStrategy : ITeleportStrategy { private const double CHARACTER_START_CREDIT = 500000; //TODO: move to DB private const int MAX_REWARD_LEVEL = 4; + private TimeSpan WAIT_TIME_BEFORE_SENDING_MAIL = TimeSpan.FromSeconds(10); private readonly TeleportDescription _description; private readonly ITrainingRewardRepository _trainingRewardRepository; @@ -92,6 +95,9 @@ 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)); }); } From 68de2a75d01362760ceeb6d34b0895a7330bb801 Mon Sep 17 00:00:00 2001 From: Loyal Servant Date: Thu, 16 May 2019 21:15:06 -0400 Subject: [PATCH 102/457] Loyal localisation (#102) * manual add from upstream official * Custom dictionary to single instance. basic sanity checks.. --- .../PerpetuumBootstrapper.cs | 1 + .../SignInRequestHandler.cs | 3 +- src/Perpetuum/CustomDictionary.cs | 80 +++++++++++++++++++ src/Perpetuum/IO/FileSystem.cs | 5 ++ src/Perpetuum/IO/IFileSystem.cs | 2 + src/Perpetuum/Keywords.cs | 1 + src/Perpetuum/Perpetuum.csproj | 4 + .../Services/Relay/ILoginQueueService.cs | 2 +- .../Services/Relay/LoginQueueService.cs | 10 ++- src/Perpetuum/Services/Sessions/Session.cs | 16 +++- src/Perpetuum/packages.config | 1 + 11 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 src/Perpetuum/CustomDictionary.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 3effb884a..be86df8d2 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -512,6 +512,7 @@ private void InitContainer(string gameRoot) _builder.RegisterType().As(); _builder.RegisterType().As(); _builder.RegisterType().As(); + _builder.RegisterType().As().SingleInstance().AutoActivate(); _builder.RegisterType().AsSelf().As(); diff --git a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs index 0cce66979..c28f4862d 100644 --- a/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/SignInRequestHandler.cs @@ -67,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/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/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/Keywords.cs b/src/Perpetuum/Keywords.cs index f3b2ddf36..cbc502dd2 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -238,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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index f95f05601..0d014af9a 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -70,6 +70,9 @@ 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 @@ -165,6 +168,7 @@ + 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/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/Sessions/Session.cs b/src/Perpetuum/Services/Sessions/Session.cs index 631a26d37..3c5f4eaf9 100644 --- a/src/Perpetuum/Services/Sessions/Session.cs +++ b/src/Perpetuum/Services/Sessions/Session.cs @@ -40,7 +40,7 @@ public interface ISession void Start(); - void SignIn(int accountID, string hwHash); + void SignIn(int accountID, string hwHash, int language); void SignOut(); void SelectCharacter(Character character); @@ -59,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; @@ -74,6 +75,7 @@ public class Session : ISession public Session(GlobalConfiguration globalConfiguration, IAccountManager accountManager, IZoneManager zoneManager, + ICustomDictionary customDictionary, Socket socket, Func commandFactory, RequestHandlerFactory requestHandlerFactory, @@ -87,6 +89,7 @@ public Session(GlobalConfiguration globalConfiguration, _globalConfiguration = globalConfiguration; _accountManager = accountManager; _zoneManager = zoneManager; + _customDictionary = customDictionary; _commandFactory = commandFactory; _requestHandlerFactory = requestHandlerFactory; _zoneRequestHandlerFactory = zoneRequestHandlerFactory; @@ -194,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; @@ -230,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); }); 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 @@  + From 211be139b8cfb1d8858b169009f8167c415aa2f9 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 16 May 2019 21:15:50 -0400 Subject: [PATCH 103/457] ep curve v2, 750k target, powercurve sub-linear (#99) --- src/Perpetuum/Accounting/AccountManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 5b04bd53f..3ea82da89 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -106,8 +106,8 @@ public int GetLockedEpByAccount(Account account) } private const double BOOSTMULTIPLIERMAX = 25; - private const double BOOSTEXPONENT = 1.5; - private const double SERVER_DESIRED_EP_LEVEL = 500000; + private const double BOOSTEXPONENT = 0.75; + private const double SERVER_DESIRED_EP_LEVEL = 750000; public IDictionary GetEPData(Account account,Character character) { From 4df7b4f79767d7340534e0089904578af525f665 Mon Sep 17 00:00:00 2001 From: clouths Date: Sat, 25 May 2019 10:17:19 -0400 Subject: [PATCH 104/457] Welcome chat message (for new players) (#100) * - Add test command (temp command) to test send message to all method in the game admin commands - Update the TrainingExitStrategy to send message to all player in the "General chat" to let know a new player finished the tutorial * - Move the Welcome message to a static handler - Remove test command from the game admin commands * - Remove unsused imports * - Update the IChannelManager interface to support announcement using character that are from the system (not logged in) - Update the Channel Message Handler to use the correct values that are fetched from the DB and is also using the new announcement method to talk to the welcome channel * - Add message at the initial log-in of new players - Update message handler to support more messages easily * make name-values into enums for DB lookups on premade msg and mail tables --- .../Characters/CharacterCreate.cs | 6 +++ src/Perpetuum/Perpetuum.csproj | 3 ++ .../Services/Channels/ChannelManager.cs | 11 +++++ .../Channels/ChannelMessageHandler.cs | 44 +++++++++++++++++++ .../Services/Channels/IChannelManager.cs | 1 + .../Channels/PreMadeChatMessageNames.cs | 8 ++++ src/Perpetuum/Services/Mail/MailHandler.cs | 4 +- .../Services/Mail/PreMadeMailNames.cs | 8 ++++ .../Strategies/TrainingExitStrategy.cs | 11 ++++- 9 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 src/Perpetuum/Services/Channels/ChannelMessageHandler.cs create mode 100644 src/Perpetuum/Services/Channels/PreMadeChatMessageNames.cs create mode 100644 src/Perpetuum/Services/Mail/PreMadeMailNames.cs diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs index bd25d6e8e..508ef53b1 100644 --- a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs +++ b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs @@ -22,6 +22,7 @@ namespace Perpetuum.RequestHandlers.Characters public class CharacterCreate : IRequestHandler { 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; @@ -110,6 +111,11 @@ public void HandleRequest(IRequest request) 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 0d014af9a..7a86857b3 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -283,8 +283,11 @@ + + + diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index 128d7e23d..fc379aa95 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -272,6 +272,17 @@ public void Talk(string channelName, Character sender, string message, IRequest } + 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) { if (issuer == character) 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/IChannelManager.cs b/src/Perpetuum/Services/Channels/IChannelManager.cs index 36261041d..688ae0444 100644 --- a/src/Perpetuum/Services/Channels/IChannelManager.cs +++ b/src/Perpetuum/Services/Channels/IChannelManager.cs @@ -21,6 +21,7 @@ public interface IChannelManager void SetPassword(string channelName, Character issuer, string password); void SetTopic(string channelName, Character issuer, string topic); 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); 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/Mail/MailHandler.cs b/src/Perpetuum/Services/Mail/MailHandler.cs index f21fb90b4..3dbab2ffe 100644 --- a/src/Perpetuum/Services/Mail/MailHandler.cs +++ b/src/Perpetuum/Services/Mail/MailHandler.cs @@ -188,12 +188,12 @@ public static int NewMailCount(Character character) public static ErrorCodes SendWelcomeMailBeginTutorial(Character newPlayer) { - return SendWelcomeMail(newPlayer, "TutorialArriveMail"); + return SendWelcomeMail(newPlayer, PreMadeMailNames.TUTORIAL_ARRIVE); } public static ErrorCodes SendWelcomeMailExitTutorial(Character newPlayer) { - return SendWelcomeMail(newPlayer, "TutorialCompleteMail"); + return SendWelcomeMail(newPlayer, PreMadeMailNames.TUTORIAL_FINISH); } private static ErrorCodes SendWelcomeMail(Character newPlayer, string mailName) 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/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index e92549040..5909f7cdf 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -19,6 +19,7 @@ public class TrainingExitStrategy : ITeleportStrategy private const double CHARACTER_START_CREDIT = 500000; //TODO: move to DB 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; @@ -97,7 +98,15 @@ public void DoTeleport(Player player) player.RemoveFromZone(); Task.Delay(WAIT_TIME_BEFORE_SENDING_MAIL) - .ContinueWith(task => MailHandler.SendWelcomeMailExitTutorial(character)); + .ContinueWith(task => + { + MailHandler.SendWelcomeMailExitTutorial(character); + }); + Task.Delay(WAIT_TIME_BEFORE_SENDING_WELCOME_MESSAGE) + .ContinueWith(task => + { + ChannelMessageHandler.SendWelcomeMessageExitTutorial(_channelManager, character.Nick); + }); }); } From 5d2e3774346559e7fc0fd48b4ef80f13cae1a147 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 25 May 2019 10:17:34 -0400 Subject: [PATCH 105/457] The Blinder (#97) * The blinder --- .../PerpetuumBootstrapper.cs | 9 ++++-- .../DefinitionNames.cs | 1 + .../Modules/EffectModules/EffectModule.cs | 9 +++++- .../EffectModules/TargetBlinderModule.cs | 31 +++++++++++++++++++ src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Units/Unit.Visibility.cs | 2 +- src/Perpetuum/Zones/NpcSystem/Turret.cs | 19 +++++++++++- 7 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index be86df8d2..338949684 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -939,6 +939,7 @@ private void RegisterEntities() RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); + RegisterEffectModule(); //TODO new module RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); @@ -1220,9 +1221,13 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] //TODO new CalibrationTemplateItem -- activates like paint! same category! ByNamePatternAndFlag("_CT_capsule", CategoryFlags.cf_lottery_items); - // TODO new ep boost item -- activates like paint - ByNamePatternAndFlag("def_boost_ep", CategoryFlags.cf_lottery_items); + // TODO new ep boost item -- activates like paint + ByNamePatternAndFlag("def_boost_ep", CategoryFlags.cf_lottery_items); + // TODO new Blinder module + ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODUL, CategoryFlags.cf_target_painter); + + //New Relic Definition for Units ByNamePatternAndFlag(DefinitionNames.RELIC, CategoryFlags.undefined); ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 52db9a669..3f7165e4a 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4511,6 +4511,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_MODUL = "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"; 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/TargetBlinderModule.cs b/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs new file mode 100644 index 000000000..fe2a036a5 --- /dev/null +++ b/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs @@ -0,0 +1,31 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 7a86857b3..24fdcea32 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -253,6 +253,7 @@ + diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index acec40221..4b2025e47 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -105,7 +105,7 @@ protected virtual bool IsDetected(Unit target) return true; } - var range = 100 / target.StealthStrength * DetectionStrength; + var range = 100 / Math.Max(1, target.StealthStrength) * Math.Max(1, DetectionStrength); return IsInRangeOf3D(target, range); } diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 4399aee45..7aea8cde1 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -27,7 +27,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private readonly IntervalTimer _timer; private readonly ActiveModule _module; @@ -179,6 +180,22 @@ public void Visit(BlobEmissionModulatorModule module) } + private const double BLINDER_CORE_THRESHOLD = 0.5; + + public void Visit(TargetBlinderModule module) + { + if (module.ParentRobot.CorePercentage < BLINDER_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) From 0eebfdd83b98d3b63a65303b737cfa87463c00d9 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Thu, 6 Jun 2019 03:22:46 +0100 Subject: [PATCH 106/457] Fixes #123 - cast to uint to prevent sign extension fill (#103) --- src/Perpetuum.RequestHandlers/Chat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 65f5791dcc49dbf48f0bcb8c90e7526ebcac5daf Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 8 Jun 2019 14:09:54 -0400 Subject: [PATCH 107/457] README and misc updates (#101) --- .gitattributes | 3 ++- CONTRIBUTING.md | 43 +++++++++++++++++++++++++++++++++++++++++++ LICENSE | 6 +++--- README.md | Bin 40 -> 1338 bytes 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.gitattributes b/.gitattributes index 1ff0c4230..cd398190e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto +*.md text ############################################################################### # Set default behavior for command prompt diff. @@ -10,7 +11,7 @@ # default for csharp files. # Note: This is only used by command line ############################################################################### -#*.cs diff=csharp +*.cs text diff=csharp ############################################################################### # Set the merge driver for project and solution files diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..2ef339f18 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# 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** + +## OP-project - Cross Repo issue and feature planning +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 OP-Project issue. When a server change requires a DB change, use the issue too as a mechanism to indicate the coupling. + +## 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 99ece67c9..9fb04ade2 100644 --- a/LICENSE +++ b/LICENSE @@ -30,7 +30,7 @@ OpenPerpetuum - LICENSE MIT License -Copyright (c) 2018 OpenPerpetuum +Copyright (c) 2018-2019 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 @@ -40,8 +40,8 @@ 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 substaintial portions -of the Software will include attributions to the OpenPerpetuum Project. +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, diff --git a/README.md b/README.md index 7ae3e95a78b1a50dd05f3c10584b121c29239230..cb1793861824fb44b7b7152d7e1df403a5d5cc9a 100644 GIT binary patch literal 1338 zcma)6+fLg+5Pi>A45}(MQAnuNi%Oq>f{Ka=Qd0U5^x zPX%NfI9Xvp2WO5jC~G_{YjX7aT^&N}kEhd0g*s-F+&0r4^~`8xsQ06mZCj>_rXZK370OXAT+dpUFO1zdd&ehw4Gdt~XWt@a2o*NO=w4L@&=k)^4VeIe zu=&b)ygxd63oEOm3B=rj;%#&}L921YPdat!B>`a$uY5qp*m><@hT8g|Oa<*$++;9V zSh%ywBu@*b97CxXgc%@5tz{fNy9e>hf2}F=D$q9OgMWCO?vx(}%IhJlWiYU@5!Q;~ zQG44nE^l`}=aGA{>5*AS<0qA->Fwv!k5AVp7iSmE{Oam4d>XQi%Q9isxFSw+0}qkd zsWbsI8$Bxpcg~Y8* Date: Thu, 13 Jun 2019 21:57:56 -0400 Subject: [PATCH 108/457] Ep curve v3 - linear with 25x hold (#104) * just make it linear * gaurantee 25x when starting --- src/Perpetuum/Accounting/AccountManager.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 3ea82da89..149cafcf6 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -106,8 +106,8 @@ public int GetLockedEpByAccount(Account account) } private const double BOOSTMULTIPLIERMAX = 25; - private const double BOOSTEXPONENT = 0.75; private const double SERVER_DESIRED_EP_LEVEL = 750000; + private const double GAURANTEED_BOOST_MAX_THRESH = 45000; public IDictionary GetEPData(Account account,Character character) { @@ -146,10 +146,13 @@ private static double GetBoostMultiplier(double boostFactor, double bonusIncreas return ((BOOSTMULTIPLIERMAX - 1) * boostFactor) + bonusIncrease + itemIncrease; } - private static double GetExperienceBoostingFactor(int collectedEpSum,double epLevelThreshold) + 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 - (Math.Pow(linearRatio,BOOSTEXPONENT)); + var result = 1.0 - linearRatio; result = result.Clamp(); return result; From 4d08903358f8e8decd1c88fcbdb201f7dfb59d8b Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 13 Jul 2019 18:31:24 -0400 Subject: [PATCH 109/457] Alliance SAP: reduce effect (#109) * alliance sap tweak - ally sap winning affects stability at different rate, rate=0 --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 3bcd1d0fe..a56f7578d 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -358,17 +358,23 @@ private void HandleTakeOver(SAP sap) var ownerEid = siteInfo.Owner ?? default(long); var ownerAndWinnerGoodRelation = false; + //Ally relationship threshold var friendlyOnly = 10; - //Compare both relations between corps: - //True IFF both corps have strictly friendly relations with eachother + //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 OR winner is in good standing with owner - if (winnerCorporation.Eid == siteInfo.Owner || ownerAndWinnerGoodRelation) + //Stability increase if winner is owner, 0 increase if ally, else negative + if (winnerCorporation.Eid == siteInfo.Owner) { newStability = (newStability + sap.StabilityChange).Clamp(0, 100); } + else if (ownerAndWinnerGoodRelation) + { + newStability = (newStability + (int)(sap.StabilityChange * allyAffectFactor)).Clamp(0, 100); + } else { newStability = (newStability - sap.StabilityChange).Clamp(0, 100); From 0858e42d21a290976e400c84f718f14d3fdcf443 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 13 Jul 2019 18:31:43 -0400 Subject: [PATCH 110/457] GM command: change corp name (#105) * corp name change by command * check uniqueness --- .../Services/Channels/GameAdminCommands.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 008354c59..6e62df231 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -1,4 +1,5 @@ using Perpetuum.Accounting.Characters; +using Perpetuum.Groups.Corporations; using Perpetuum.ExportedTypes; using Perpetuum.GenXY; using Perpetuum.Host.Requests; @@ -770,6 +771,20 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C } + if (command[0] == "#renamecorp") + { + string currentCorpName = command[1]; + string desiredCorpName = command[2]; + string desiredCorpNick = command[3]; + + Corporation.IsNameOrNickTaken(desiredCorpName, desiredCorpNick).ThrowIfTrue(ErrorCodes.NameTaken); + var corp = Corporation.GetByName(currentCorpName); + corp.SetName(desiredCorpName, desiredCorpNick); + + channel.SendMessageToAll(sessionManager, sender, string.Format("Corp with nick {0} changed to: {1} [{2}]", currentCorpName, desiredCorpName, desiredCorpNick)); + } + + //FreeAllLockedEP for account - by request of player if (command[0] == "#unlockallep") { From 5834f29443882bbdce8eb16983fbcae6df0c9344 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Sat, 13 Jul 2019 23:31:59 +0100 Subject: [PATCH 111/457] 100% Corp tax no longer exits the reward loop (#106) --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index 96a4dd17e..c2877a2a0 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1568,11 +1568,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); From 34b1469fd3186703e4ad874de60ca2b508c0b118 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 1 Sep 2019 13:38:36 -0400 Subject: [PATCH 112/457] Outpost Stability cap increase + fixes (#108) * Stability cap increase, production point stabilize at old threshold , refactor * stability to 120 --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index a56f7578d..0b8872c7e 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -26,6 +26,8 @@ public class Outpost : DockingBase { private const int EP_WINNER = 120; private const int EP_LOSER = 20; + private const int MAX_STABILITY = 120; + private const int PRODUCTION_BONUS_THRESHOLD = 100; private TimeRange _intrusionWaitTime => IntrusionWaitTime; private readonly IEntityServices _entityServices; @@ -369,7 +371,7 @@ private void HandleTakeOver(SAP sap) //Stability increase if winner is owner, 0 increase if ally, else negative if (winnerCorporation.Eid == siteInfo.Owner) { - newStability = (newStability + sap.StabilityChange).Clamp(0, 100); + newStability = (newStability + sap.StabilityChange).Clamp(0, MAX_STABILITY); } else if (ownerAndWinnerGoodRelation) { @@ -377,7 +379,7 @@ private void HandleTakeOver(SAP sap) } else { - newStability = (newStability - sap.StabilityChange).Clamp(0, 100); + newStability = (newStability - sap.StabilityChange).Clamp(0, MAX_STABILITY); // csak akkor ha 0 lett a stability if (newStability == 0) @@ -535,6 +537,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); @@ -564,6 +569,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); From 9e81f6733222eed3bacfb50352208a1bfa4ec275 Mon Sep 17 00:00:00 2001 From: Loyal Servant Date: Sun, 22 Sep 2019 14:43:28 -0400 Subject: [PATCH 113/457] Sort a few issues with NPC threat management and EP rewards (#95) * This sorts a few issues: 1. The threat manger only removed the first entry in the list. (see ProcessHostiles) 2. The It was possible to act on a dirty list in the threatmanager. I removed all of the logic to maintain a clean and dirty list - I feel this implementation was overkill so this greatly simplifies things. 3. If an NPC cannot path to a player for whatever reason but that player is damaging that NPC they will be added to a list to get half EP. * Psuedothreat object as a unit with timer --- src/Perpetuum/Zones/NpcSystem/Npc.cs | 82 ++++++++++++++++++- .../Zones/NpcSystem/ThreatManager.cs | 29 +------ 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index d4ac265a0..a1fe4af66 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -168,6 +168,11 @@ private bool IsAttackable(Hostile hostile) if (hostile.unit.IsInvulnerable) return false; + if (hostile.unit is Player player) + { + return player.GetLocks().OfType().Where(x => x.Target.Eid == npc.Eid).Any(); + } + if (npc.Behavior.Type == NpcBehaviorType.Neutral) { if (hostile.IsExpired) @@ -183,11 +188,14 @@ private bool IsAttackable(Hostile hostile) private void ProcessHostiles() { - foreach (var hostile in npc.ThreatManager.Hostiles) + var hostileEnumerator = npc.ThreatManager.Hostiles.GetEnumerator(); + while (hostileEnumerator.MoveNext()) { + var hostile = hostileEnumerator.Current; if (!IsAttackable(hostile)) { npc.ThreatManager.Remove(hostile); + npc.AddPseudoThreat(hostile.unit); continue; } @@ -373,6 +381,7 @@ private void UpdateHostile(TimeSpan time) if (path == null) { npc.ThreatManager.Remove(mostHated); + npc.AddPseudoThreat(mostHated.unit); return; } @@ -558,6 +567,34 @@ public AggressiveBehavior() : base(NpcBehaviorType.Aggressive) } } + public class PsuedoThreat + { + private TimeSpan _lastUpdated = TimeSpan.Zero; + private TimeSpan Expiration = TimeSpan.FromMinutes(1); + + public PsuedoThreat(Unit unit) + { + Unit = unit; + } + + public Unit Unit { get; } + + public bool IsExpired + { + get { return _lastUpdated > Expiration; } + } + + public void RefreshThreat() + { + _lastUpdated = TimeSpan.Zero; + } + + public void Update(TimeSpan time) + { + _lastUpdated += time; + } + } + public class Npc : Creature,ITaggable { private readonly TagHelper _tagHelper; @@ -565,12 +602,15 @@ public class Npc : Creature,ITaggable private readonly ThreatManager _threatManager; private object _bestCombatRange; private TimeSpan _lastHelpCalled; + private List PseudoThreats; + Object PseudoLock = new Object(); public Npc(TagHelper tagHelper) { _tagHelper = tagHelper; _threatManager = new ThreatManager(); AI = new StackFSM(); + PseudoThreats = new List(); } public NpcBehavior Behavior { get; set; } @@ -624,7 +664,17 @@ public Player GetTagger() public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) { - _threatManager.GetHostile(hostile).AddThreat(threat); + _threatManager.GetOrAddHostile(hostile).AddThreat(threat); + + var PseudoThreat = PseudoThreats.Where(x => x.Unit == hostile).FirstOrDefault(); + if (PseudoThreat != null) + { + lock (PseudoLock) + { + PseudoThreats.Remove(PseudoThreat); + Logger.DebugInfo(" Removed " + PseudoThreat.ToString()); + } + } if (!spreadToGroup) return; @@ -643,6 +693,23 @@ public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) } } + public void AddPseudoThreat(Unit hostile) + { + lock (PseudoLock) + { + PseudoThreats.Add(new PsuedoThreat(hostile)); + } + } + + private void UpdatePseudoThreats(TimeSpan time) + { + lock (PseudoLock) + { + PseudoThreats.ForEach(threat => threat.Update(time)); + PseudoThreats.RemoveAll(threat => threat.IsExpired); + } + } + public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) @@ -782,6 +849,14 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(hostile.unit); hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep); } + + // same as above, but add half EP for players that were out of range of the NPC. + var psuedoHostiles = PseudoThreats.Where(p => !ThreatManager.Contains(p.Unit)); + foreach (var hostile in psuedoHostiles) + { + var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(hostile.Unit); + hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep / 2); + } } scope.Complete(); @@ -865,9 +940,10 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); Behavior.Update(time); - _threatManager.Update(); AI.Update(time); + + UpdatePseudoThreats(time); } protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs index e21955f00..0f7433cbc 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs @@ -132,29 +132,20 @@ public static Hostile GetMostHatedHostile(this IThreatManager manager) public class ThreatManager : IThreatManager { - private ImmutableSortedSet _sortedHotiles = ImmutableSortedSet.Empty; private ImmutableDictionary _hostiles = ImmutableDictionary.Empty; - private bool _isDirty; - public Hostile GetHostile(Unit unit) + public Hostile GetOrAddHostile(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; } + get { return _hostiles.Values.ToImmutableSortedSet(); } } public bool Contains(Unit unit) @@ -165,26 +156,14 @@ public bool Contains(Unit unit) 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(); + ImmutableInterlocked.TryRemove(ref _hostiles, hostile.unit.Eid, out hostile); } - public string ToDebugString() + public string ToDebugString() { if ( _hostiles.Count == 0 ) return string.Empty; From daf0ea777d59d19577f19a4606a4a6d1befdb710 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 22 Sep 2019 15:45:52 -0400 Subject: [PATCH 114/457] Event System + SAP-NPCs (#107) * Initial mockup of event system on npc deaths * Using NPCBossInfo to derive outpost relation * capture duplicate logic * refactor constants and some operations * stability effect with override relation-based +/-, refactors, and cleanuo * remove onaggro message call, TODO for another time * docstrings on bossinfo * refactor, docs * docs and format * sap takeover logic bug * splitable loot bug fix --- .../PerpetuumBootstrapper.cs | 17 ++ src/Perpetuum/Perpetuum.csproj | 9 + .../EventServices/EventListenerService.cs | 69 ++++++ .../EventMessages/EventMessage.cs | 9 + .../EventMessages/EventMessageSimple.cs | 20 ++ .../EventMessages/NpcEventMessage.cs | 31 +++ .../EventMessages/StabilityEffectMessage.cs | 43 ++++ .../EventProcessors/AffectOutpostStability.cs | 28 +++ .../EventServices/EventProcessors/ChatEcho.cs | 67 ++++++ .../EventProcessors/EventProcessor.cs | 23 ++ src/Perpetuum/Zones/Intrusion/Outpost.cs | 224 ++++++++++-------- src/Perpetuum/Zones/Intrusion/SAP.cs | 16 ++ src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 6 +- .../NpcSystem/Flocks/FlockConfiguration.cs | 1 + .../Flocks/FlockConfigurationRepository.cs | 1 + .../NpcSystem/Flocks/IFlockConfiguration.cs | 1 + .../Zones/NpcSystem/Flocks/NormalFlock.cs | 4 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 160 +++++++++++++ src/Perpetuum/Zones/NpcSystem/Npc.cs | 31 ++- 19 files changed, 652 insertions(+), 108 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventListenerService.cs create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 338949684..c5229cbf3 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -78,6 +78,7 @@ using Perpetuum.Robots; using Perpetuum.Services.Channels; using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Services.ExtensionService; using Perpetuum.Services.HighScores; using Perpetuum.Services.Insurance; @@ -836,6 +837,10 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit().OnActivated(e => { + var listener = new AffectOutpostStability(e.Instance); + e.Context.Resolve().AttachListener(listener); + + #if (DEBUG) var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromSeconds(10),TimeSpan.FromSeconds(15)); #else @@ -1651,6 +1656,18 @@ private void InitRelayManager() e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1))); }); + //TODO new EventListenerService + _builder.RegisterType(); + _builder.RegisterType(); + _builder.RegisterType(); + _builder.RegisterType().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(2.5))); + e.Instance.AttachListener(e.Context.Resolve()); + e.Instance.AttachListener(e.Context.Resolve()); + }); + + _builder.RegisterType().As(); _builder.RegisterType(); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 24fdcea32..674d1eb7c 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -287,7 +287,15 @@ + + + + + + + + @@ -659,6 +667,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventListenerService.cs b/src/Perpetuum/Services/EventServices/EventListenerService.cs new file mode 100644 index 000000000..8afca4c7d --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventListenerService.cs @@ -0,0 +1,69 @@ +using Perpetuum.Threading.Process; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; + +namespace Perpetuum.Services.EventServices +{ + /// + /// Listener Process that processes queue of EventMessages and notifies observers + /// + public class EventListenerService : Process + { + private IList> _observers; + private ConcurrentQueue _queue; + + public EventListenerService() + { + _observers = new List>(); + _queue = new ConcurrentQueue(); + } + + /// + /// Send a message where the type of EventMessage determines which listener is notified + /// + /// EventMessage of the type + public void PublishMessage(EventMessage message) + { + _queue.Enqueue(message); + } + + public void NotifyListeners(EventMessage message) + { + foreach (var obs in _observers) + { + obs.OnNext(message); + } + } + + /// + /// Listeners are subscribed and instantiated in the Bootstrapper + /// + /// Listener + public void AttachListener(EventProcessor observer) + { + _observers.Add(observer); + } + + + public override void Update(TimeSpan time) + { + 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/EventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs new file mode 100644 index 000000000..a245269ea --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Services.EventServices.EventMessages +{ + /// + /// A specification Interface for EventMessages + /// + public interface EventMessage + { + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs b/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs new file mode 100644 index 000000000..7a05419c0 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs @@ -0,0 +1,20 @@ +namespace Perpetuum.Services.EventServices.EventMessages +{ + /// + /// A Simple EventMessage stub with string content, mostly to demonstrate usage + /// + public class EventMessageSimple : EventMessage + { + private string _content; + + public EventMessageSimple(string payload) + { + _content = payload; + } + + public string GetMessage() + { + return _content; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs new file mode 100644 index 000000000..c2a04f7c6 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs @@ -0,0 +1,31 @@ +using Perpetuum.Players; +using Perpetuum.Units; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + + /// + /// EventMessage sent by an NPC + /// + public class NpcMessage : EventMessage + { + 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/StabilityEffectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs new file mode 100644 index 000000000..f95d9bb0f --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs @@ -0,0 +1,43 @@ +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 : EventMessage + { + private Player _player; + private IList _particpants; + public Outpost Outpost { get; } + + public bool OverrideRelations { get; } + public int StabilityChange { get; } + public int Definition { get; } + public long Eid { get; } + public StabilityAffectingEvent(Outpost outpost, Player winner, int def, long eid, int sapPoints, IList participants, bool overrideRelations = false) + { + Outpost = outpost; + _player = winner; + StabilityChange = sapPoints; + Definition = def; + Eid = eid; + _particpants = participants; + OverrideRelations = overrideRelations; + } + + public IList GetPlayers() + { + return _particpants; + } + + [CanBeNull] + public Corporation GetWinnerCorporation() + { + return Corporation.Get(_player.CorporationEid); + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs new file mode 100644 index 000000000..abd68c8ab --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs @@ -0,0 +1,28 @@ +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 Outpost _outpost; + public AffectOutpostStability(Outpost outpost) + { + _outpost = outpost; + } + + public override void OnNext(EventMessage 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..fa005ee89 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs @@ -0,0 +1,67 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; +using Perpetuum.Units; +using Perpetuum.Zones; + +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 Character _announcer; + + public ChatEcho(IChannelManager channelManager) + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + _channelManager = channelManager; + } + + public override void OnNext(EventMessage value) + { + if (value is EventMessageSimple msg) + { + _channelManager.Announcement("General chat", _announcer, msg.GetMessage()); + } + + } + + } + + /// + /// NPC vicinity Chat Echo event handler + /// Emits a message from an NPC on the Vicinity chat channel + /// + public class NpcChatEcho : EventProcessor + { + private readonly IChannelManager _channelManager; + private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; //TODO "Nian" character + private Character _announcer; + + public NpcChatEcho(IChannelManager channelManager) + { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); + _channelManager = channelManager; + } + + public override void OnNext(EventMessage 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/EventProcessor.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs new file mode 100644 index 000000000..f459ef1ed --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs @@ -0,0 +1,23 @@ +using System; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + /// + /// Base class for all EventProcessors + /// + /// + public abstract class EventProcessor : IObserver + { + public abstract void OnNext(EventMessage value); + + void IObserver.OnCompleted() + { + throw new NotImplementedException(); + } + + void IObserver.OnError(Exception error) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 0b8872c7e..f53e7b3f2 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -15,6 +15,7 @@ using Perpetuum.Items.Templates; using Perpetuum.Log; using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.Looting; using Perpetuum.Timers; using Perpetuum.Units.DockingBases; @@ -25,8 +26,9 @@ 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 = 120; + private const int MIN_STABILITY = 0; + private const int STARTING_STABILITY = 1; private const int PRODUCTION_BONUS_THRESHOLD = 100; private TimeRange _intrusionWaitTime => IntrusionWaitTime; @@ -318,8 +320,6 @@ 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 /// @@ -331,116 +331,144 @@ 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) - { - //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).Clamp(0, MAX_STABILITY); - } - else if (ownerAndWinnerGoodRelation) - { - newStability = (newStability + (int)(sap.StabilityChange * allyAffectFactor)).Clamp(0, 100); - } - else - { - newStability = (newStability - sap.StabilityChange).Clamp(0, MAX_STABILITY); - - // 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) + { + var winnerCorporation = sap.GetWinnerCorporation(); + if (winnerCorporation == null) + return; - //set the resulting values - SetIntrusionOwnerAndPoints(newOwner, newStability); - ReactStabilityChanges(siteInfo, oldStability, newStability, newOwner, oldOwner); - } + var siteInfo = GetIntrusionSiteInfo(); + var oldStability = siteInfo.Stability; + var newStability = siteInfo.Stability; + var newOwner = siteInfo.Owner; + var oldOwner = siteInfo.Owner; - logEvent.NewOwner = newOwner; - logEvent.NewStability = newStability; - InsertIntrusionLog(logEvent); + 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) + { + //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 || sap.OverrideRelations) + { + newStability = (newStability + sap.StabilityChange); + } + else if (ownerAndWinnerGoodRelation) + { + newStability = (newStability + (int)(sap.StabilityChange * allyAffectFactor)); + } + else + { + newStability = (newStability - sap.StabilityChange); + } + + if (siteInfo.Owner == null) + { + // No owner - winner gets outpost, for any SAP event + logEvent.EventType = IntrusionEvents.siteOwnershipGain; + newOwner = winnerCorporation.Eid; + newStability = STARTING_STABILITY; + } + else if (newStability <= 0) + { + // Outpost has owner, but new stability hit 0 = owner loses station + logEvent.EventType = IntrusionEvents.siteOwnershipLost; + newOwner = null; + } - 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); - } + newStability = newStability.Clamp(MIN_STABILITY, MAX_STABILITY); - //make dem toast anyways - Transaction.Current.OnCommited(() => - { - if (oldStability != newStability) - { - OnIntrusionSiteInfoUpdated(); - InformAllPlayers(); - } + //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.GetPlayers()) + { + player.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Intrusion, EP_WINNER); + } + + //make dem toast anyways + Transaction.Current.OnCommited(() => + { + if (oldStability != newStability) + { + OnIntrusionSiteInfoUpdated(); + InformAllPlayers(); + } - InformPlayersOnZone(Commands.ZoneSapActivityEnd, new Dictionary + 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); - } - } + }); } diff --git a/src/Perpetuum/Zones/Intrusion/SAP.cs b/src/Perpetuum/Zones/Intrusion/SAP.cs index 191b3d905..590e19876 100644 --- a/src/Perpetuum/Zones/Intrusion/SAP.cs +++ b/src/Perpetuum/Zones/Intrusion/SAP.cs @@ -9,6 +9,7 @@ using Perpetuum.Groups.Corporations; using Perpetuum.Log; using Perpetuum.Players; +using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Beams; @@ -321,5 +322,20 @@ 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 winner = GetPlayerTopScores(1)[0].character.GetPlayerRobotFromZone(); + return new StabilityAffectingEvent(Site, winner, Definition, Eid, StabilityChange, players); + } } } \ 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 8a81e7e33..2b0563820 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -26,9 +26,12 @@ public class Flock 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; @@ -115,6 +118,7 @@ protected virtual void CreateMemberInZone() var npc = (Npc)EntityService.Factory.Create(Configuration.EntityDefault, EntityIDGenerator.Random); npc.Behavior = GetBehavior(); npc.SpecialType = Configuration.SpecialType; + npc.BossInfo = BossInfo; var gen = new CompositeLootGenerator( new LootGenerator(LootService.GetNpcLootInfos(npc.Definition)), diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs index 77c3c0a4c..dda773c7c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs @@ -18,6 +18,7 @@ public class FlockConfiguration : IFlockConfiguration public bool Enabled { get; set; } public NpcBehaviorType 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; } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs index 37d5afafc..72a983bed 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -40,6 +40,7 @@ public void LoadAllConfig() c.Enabled = r.GetValue("enabled"); c.BehaviorType = (NpcBehaviorType) r.GetValue("behaviorType"); c.SpecialType = (NpcSpecialType) r.GetValue("npcSpecialType"); + c.BossInfo = NpcBossInfo.GetBossInfoByFlockID(c.ID); }); var config = builder.Build(); _flockConfigurations[config.ID] = config; diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs index 00114807b..c1e601c69 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs @@ -21,5 +21,6 @@ public interface IFlockConfiguration bool IsCallForHelp { get; } NpcBehaviorType BehaviorType { get; } NpcSpecialType SpecialType { get; } + NpcBossInfo BossInfo { get; } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index 1b8518ce2..ad1b88168 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs @@ -71,9 +71,9 @@ protected override void OnMemberDead(Unit killer,Unit npc) private TimeSpan GetRespawnTime() { - if (Configuration.SpecialType == NpcSpecialType.Boss) + if (IsBoss) { - return RespawnTime.Multiply(FastRandom.NextDouble(0.9, 1.15)); + return BossInfo.OnRespawn(RespawnTime); } return RespawnTime; } diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs new file mode 100644 index 000000000..a41b6d475 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -0,0 +1,160 @@ +using Perpetuum.Data; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Units; +using Perpetuum.Zones.Intrusion; +using System; +using System.Collections.Generic; +using System.Data; +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 + { + public static 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 info = new NpcBossInfo(id, flockid, respawnFactor, lootSplit, outpostEID, stabilityPts, overrideRelations, deathMessage, aggressMessage); + + return info; + } + + public static NpcBossInfo GetBossInfoByFlockID(int flockid) + { + var bossInfos = Db.Query() + .CommandText(@"SELECT TOP 1 id, flockid, respawnNoiseFactor, lootSplitFlag, outpostEID, stabilityPts, overrideRelations, customDeathMessage, customAggressMessage + FROM dbo.npcbossinfo WHERE flockid=@flockid;") + .SetParameter("@flockid", flockid) + .Execute() + .Select(CreateBossInfoFromDB); + + return bossInfos.SingleOrDefault(); + } + + private readonly int _id; + private readonly int _flockid; + private readonly double? _respawnNoiseFactor; + private readonly bool _lootSplit; + private readonly long? _outpostEID; + private readonly int? _stabilityPts; + private readonly bool _overrideRelations; + private readonly string _deathMsg; + private readonly string _aggroMsg; + + public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg) + { + _id = id; + _flockid = flockid; + _respawnNoiseFactor = respawnNoiseFactor; + _lootSplit = lootSplit; + _outpostEID = outpostEID; + _stabilityPts = stabilityPts; + _overrideRelations = overrideRelations; + _deathMsg = customDeathMsg; + _aggroMsg = customAggroMsg; + } + + // TODO - find good place to call this that will be stable and not noisey (excessive repeat messages) + /// + /// Handle any actions that this NPC Boss should do upon Aggression, including sending a message + /// + /// Player aggressor + /// the npc event channel + public void OnAggro(Unit aggressor, EventListenerService channel) + { + CommunicateAggression(aggressor, channel); + } + + /// + /// 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 + /// npc-event listener channel + public void OnDeath(Npc npc, Unit killer, EventListenerService channel) + { + CommunicateDeath(killer, channel); + if (_outpostEID != null) + { + 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(); + EventMessage sapMessage = new StabilityAffectingEvent(outpost as Outpost, + zone.ToPlayerOrGetOwnerPlayer(killer), + npc.Definition, + npc.Eid, + StabilityPoints(), + participants, + OverrideRelations()); + channel.PublishMessage(sapMessage); + } + } + } + + /// + /// Apply any respawn timer modifiers + /// + /// normal respawn time of npc + /// modified respawn time of npc + public TimeSpan OnRespawn(TimeSpan respawnTime) + { + var factor = _respawnNoiseFactor ?? 0.0; + return respawnTime.Multiply(FastRandom.NextDouble(1.0 - factor, 1.0 + factor)); + } + + /// + /// True if this NPC Boss should use auto-loot splitting + /// + /// true - if loot needs splitting + public bool IsLootSplit() + { + return _lootSplit; + } + + private bool OverrideRelations() + { + return _overrideRelations; + } + + private int StabilityPoints() + { + return _stabilityPts ?? 0; + } + + private void CommunicateAggression(Unit aggressor, EventListenerService channel) + { + SendMessage(aggressor, channel, _aggroMsg); + } + + private void CommunicateDeath(Unit aggressor, EventListenerService channel) + { + SendMessage(aggressor, channel, _deathMsg); + } + + private static void SendMessage(Unit src, EventListenerService eventChannel, string msg) + { + if (!msg.IsNullOrEmpty()) + { + EventMessage eventMessage = new NpcMessage(msg, src); + Task.Run(() => eventChannel.PublishMessage(eventMessage)); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index a1fe4af66..d9d0605c2 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -15,6 +15,7 @@ using Perpetuum.Modules.Weapons; using Perpetuum.PathFinders; using Perpetuum.Players; +using Perpetuum.Services.EventServices; using Perpetuum.Services.Looting; using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; @@ -602,11 +603,13 @@ public class Npc : Creature,ITaggable private readonly ThreatManager _threatManager; private object _bestCombatRange; private TimeSpan _lastHelpCalled; + private readonly EventListenerService _eventChannel; private List PseudoThreats; - Object PseudoLock = new Object(); + private object PseudoLock = new Object(); - public Npc(TagHelper tagHelper) + public Npc(TagHelper tagHelper, EventListenerService eventChannel) { + _eventChannel = eventChannel; _tagHelper = tagHelper; _threatManager = new ThreatManager(); AI = new StackFSM(); @@ -615,6 +618,12 @@ public Npc(TagHelper tagHelper) public NpcBehavior Behavior { get; set; } public NpcSpecialType SpecialType { get; set; } + public NpcBossInfo BossInfo { get; set; } + + public bool IsBoss() + { + return SpecialType == NpcSpecialType.Boss && BossInfo != null; + } [CanBeNull] private INpcGroup _group; @@ -784,6 +793,11 @@ protected override void OnDead(Unit killer) Debug.Assert(zone != null, "zone != null"); HandleNpcDeadAsync(zone, killer, tagger).ContinueWith((t) => base.OnDead(killer)).LogExceptions(); + + if (IsBoss()) + { + BossInfo.OnDeath(this, killer, _eventChannel); + } } private Task HandleNpcDeadAsync(IZone zone, Unit killer, Player tagger) @@ -798,16 +812,19 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) using (var scope = Db.CreateTransaction()) { - if (SpecialType == NpcSpecialType.Boss) + if (IsBoss() && BossInfo.IsLootSplit()) { //Boss - Split loot equally to all participants List participants = new List(); participants = ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)).ToList(); - ISplittableLootGenerator splitLooter = new SplittableLootGenerator(LootGenerator); - List lootGenerators = splitLooter.GetGenerators(participants.Count); - for (var i = 0; i < participants.Count; i++) + if (participants.Count > 0) { - LootContainer.Create().SetOwner(participants[i]).AddLoot(lootGenerators[i]).BuildAndAddToZone(zone, participants[i].CurrentPosition); + 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 From c700a9c9bdd7ecf2a39c5e07ce5951c39f00ab6a Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 26 Oct 2019 18:03:33 -0400 Subject: [PATCH 115/457] Outpost Stability Decay (#110) * Initial mockup of event system on npc deaths * Using NPCBossInfo to derive outpost relation * capture duplicate logic * refactor constants and some operations * stability effect with override relation-based +/-, refactors, and cleanuo * remove onaggro message call, TODO for another time * docstrings on bossinfo * refactor, docs * docs and format * sap takeover logic bug * outpost decay * param fixes, and add Entitydef properly * decay rates v1, docs --- src/Perpetuum/Perpetuum.csproj | 1 + .../EventMessages/StabilityEffectMessage.cs | 21 ++++-- src/Perpetuum/Zones/Intrusion/Outpost.cs | 65 ++++++++++++------- src/Perpetuum/Zones/Intrusion/OutpostDecay.cs | 55 ++++++++++++++++ 4 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 src/Perpetuum/Zones/Intrusion/OutpostDecay.cs diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 674d1eb7c..eaab92d04 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -598,6 +598,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs index f95d9bb0f..20c60a3b7 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs @@ -11,24 +11,33 @@ namespace Perpetuum.Services.EventServices.EventMessages public class StabilityAffectingEvent : EventMessage { private Player _player; - private IList _particpants; + private IList _particpants = new List(); public Outpost Outpost { get; } - public bool OverrideRelations { get; } public int StabilityChange { get; } public int Definition { get; } - public long Eid { get; } - public StabilityAffectingEvent(Outpost outpost, Player winner, int def, long eid, int sapPoints, IList participants, bool overrideRelations = false) + public long? Eid { get; } + + public StabilityAffectingEvent(Outpost outpost, Player winner, int def, long? eid, int sapPoints, IList participants = null, bool overrideRelations = false) { Outpost = outpost; _player = winner; StabilityChange = sapPoints; Definition = def; Eid = eid; - _particpants = participants; + if (participants != null) + { + _particpants = participants; + } + OverrideRelations = overrideRelations; } + public bool IsSystemGenerated() + { + return _player == null; + } + public IList GetPlayers() { return _particpants; @@ -37,6 +46,8 @@ public IList GetPlayers() [CanBeNull] public Corporation GetWinnerCorporation() { + if (IsSystemGenerated()) + return Corporation.GetByName("syndicate_police_central"); return Corporation.Get(_player.CorporationEid); } } diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index f53e7b3f2..25af0a7ed 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -15,6 +15,7 @@ using Perpetuum.Items.Templates; using Perpetuum.Log; using Perpetuum.Services.Channels; +using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.Looting; using Perpetuum.Timers; @@ -32,10 +33,13 @@ public class Outpost : DockingBase private const int PRODUCTION_BONUS_THRESHOLD = 100; private TimeRange _intrusionWaitTime => IntrusionWaitTime; + private TimeSpan _timeSinceLastSAP = TimeSpan.Zero; private readonly IEntityServices _entityServices; private readonly ICorporationManager _corporationManager; private readonly ILootService _lootService; private static readonly ILookup _sapInfos; + private readonly EventListenerService _eventChannel; + private OutpostDecay _decay; public static StabilityBonusThreshold[] StabilityBonusThresholds { get; private set; } public static int DefenseNodesStabilityLimit { get; private set; } @@ -64,11 +68,14 @@ 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); } public TimeRange IntrusionWaitTime { get; set; } @@ -141,6 +148,7 @@ public void AppendSiteInfoToPacket(Packet packet) protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); + _decay.OnUpdate(time); if (!Enabled || IntrusionInProgress) return; @@ -317,6 +325,7 @@ public bool Enabled private void OnSAPTakeOver(SAP sap) { + _decay.OnSAP(); Task.Run(() => HandleTakeOver(sap)).ContinueWith(t => IntrusionInProgress = false); } @@ -373,11 +382,16 @@ public void IntrusionEvent(StabilityAffectingEvent sap) /// private void processStabilityChange(StabilityAffectingEvent sap) { + // Check for invalid player-SAPS var winnerCorporation = sap.GetWinnerCorporation(); - if (winnerCorporation == null) + if (winnerCorporation == null && !sap.IsSystemGenerated()) return; + // Check for unowned outposts to not be affected by system-generated events var siteInfo = GetIntrusionSiteInfo(); + if (siteInfo.Owner == null && sap.IsSystemGenerated()) + return; + var oldStability = siteInfo.Stability; var newStability = siteInfo.Stability; var newOwner = siteInfo.Owner; @@ -393,7 +407,11 @@ private void processStabilityChange(StabilityAffectingEvent sap) OldStability = oldStability }; - if (winnerCorporation is PrivateCorporation) + if (sap.IsSystemGenerated()) + { + newStability = (newStability + sap.StabilityChange); + } + else if (winnerCorporation is PrivateCorporation) { //Compare the Owner and Winner corp's relations var ownerEid = siteInfo.Owner ?? default(long); @@ -420,28 +438,27 @@ private void processStabilityChange(StabilityAffectingEvent sap) { newStability = (newStability - sap.StabilityChange); } - - if (siteInfo.Owner == null) - { - // No owner - winner gets outpost, for any SAP event - logEvent.EventType = IntrusionEvents.siteOwnershipGain; - newOwner = winnerCorporation.Eid; - newStability = STARTING_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); + if (siteInfo.Owner == null) + { + // No owner - winner gets outpost, for any SAP event + logEvent.EventType = IntrusionEvents.siteOwnershipGain; + newOwner = winnerCorporation.Eid; + newStability = STARTING_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); @@ -460,14 +477,16 @@ private void processStabilityChange(StabilityAffectingEvent sap) OnIntrusionSiteInfoUpdated(); InformAllPlayers(); } - - InformPlayersOnZone(Commands.ZoneSapActivityEnd, new Dictionary + if (!sap.IsSystemGenerated()) + { + InformPlayersOnZone(Commands.ZoneSapActivityEnd, new Dictionary { {k.siteEID, Eid}, {k.eventType, (int) logEvent.EventType}, {k.eid, sap.Eid}, {k.winner, winnerCorporation.Eid}, }); + } }); } diff --git a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs new file mode 100644 index 000000000..c0e622410 --- /dev/null +++ b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs @@ -0,0 +1,55 @@ +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 Outpost _outpost; + private readonly EventListenerService _eventChannel; + private readonly static TimeSpan noDecayBefore = TimeSpan.FromDays(7); + 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 readonly EntityDefault def = EntityDefault.GetByName("def_outpost_decay"); + + public OutpostDecay(EventListenerService eventChannel, Outpost outpost) + { + _outpost = outpost; + _eventChannel = eventChannel; + } + + 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 OnSAP() + { + lastSuccessfulIntrusion = TimeSpan.Zero; + } + + private void DoDecay() + { + _eventChannel.PublishMessage(new StabilityAffectingEvent(_outpost, null, def.Definition, null, decayPts)); + } + } +} From f5a4b37872c2296807c2081864974588f597924b Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 26 Oct 2019 18:05:11 -0400 Subject: [PATCH 116/457] Sap relics (#112) * Initial mockup of event system on npc deaths * Using NPCBossInfo to derive outpost relation * capture duplicate logic * refactor constants and some operations * stability effect with override relation-based +/-, refactors, and cleanuo * remove onaggro message call, TODO for another time * docstrings on bossinfo * refactor, docs * docs and format * sap takeover logic bug * outpost decay * param fixes, and add Entitydef properly * decay rates v1, docs * remove unused * abstract the relic manager, make the interface and zone subtype * ungodly refactor with SAP relic and Outpost Relic Manager implementation * got em working, almost * distance is x10 ..doh * 30 for test, 3 for realz * sap relic respawn param, balanced * decay after 3 days * max intrusion relics per outpost -> 1 --- .../PerpetuumBootstrapper.cs | 14 +- .../DefinitionNames.cs | 3 +- src/Perpetuum/Perpetuum.csproj | 10 +- src/Perpetuum/Services/Relics/RelicInfo.cs | 14 +- src/Perpetuum/Services/Relics/RelicManager.cs | 322 ------------------ .../RelicManagers/AbstractRelicManager.cs | 225 ++++++++++++ .../Relics/RelicManagers/IRelicManager.cs | 18 + .../RelicManagers/OutpostRelicManager.cs | 100 ++++++ .../Relics/RelicManagers/ZoneRelicManager.cs | 210 ++++++++++++ .../Services/Relics/RelicSpawnInfo.cs | 2 - .../{Relic.cs => Relics/AbstractRelic.cs} | 28 +- .../Services/Relics/Relics/IRelic.cs | 23 ++ src/Perpetuum/Services/Relics/Relics/Relic.cs | 14 + .../Services/Relics/Relics/SAPRelic.cs | 37 ++ src/Perpetuum/Zones/IZone.cs | 2 +- src/Perpetuum/Zones/Intrusion/Outpost.cs | 8 +- src/Perpetuum/Zones/Intrusion/OutpostDecay.cs | 2 +- src/Perpetuum/Zones/Zone.cs | 2 +- 18 files changed, 681 insertions(+), 353 deletions(-) delete mode 100644 src/Perpetuum/Services/Relics/RelicManager.cs create mode 100644 src/Perpetuum/Services/Relics/RelicManagers/AbstractRelicManager.cs create mode 100644 src/Perpetuum/Services/Relics/RelicManagers/IRelicManager.cs create mode 100644 src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs create mode 100644 src/Perpetuum/Services/Relics/RelicManagers/ZoneRelicManager.cs rename src/Perpetuum/Services/Relics/{Relic.cs => Relics/AbstractRelic.cs} (85%) create mode 100644 src/Perpetuum/Services/Relics/Relics/IRelic.cs create mode 100644 src/Perpetuum/Services/Relics/Relics/Relic.cs create mode 100644 src/Perpetuum/Services/Relics/Relics/SAPRelic.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index c5229cbf3..dc45b8991 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1010,9 +1010,10 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity();//TODO register new entitydef - RegisterEntity(); + RegisterEntity(); RegisterEntity(); - + RegisterEntity(); + _builder.Register>(x => { @@ -1234,6 +1235,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] //New Relic Definition for Units ByNamePatternAndFlag(DefinitionNames.RELIC, CategoryFlags.undefined); + ByNamePatternAndFlag(DefinitionNames.RELIC_SAP, CategoryFlags.undefined); ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); @@ -2376,9 +2378,9 @@ private void RegisterRifts() private void RegisterRelics() { - _builder.RegisterType(); + _builder.RegisterType().As(); - _builder.Register>(x => + _builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2405,7 +2407,7 @@ private void RegisterRelics() 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)); + return ctx.Resolve(new TypedParameter(typeof(IZone), zone)); }; }); } @@ -2488,7 +2490,7 @@ void RegisterZone(ZoneType type) where T: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); + zone.RelicManager = ctx.Resolve>().Invoke(zone); if (configuration.Terraformable) { diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 3f7165e4a..7225cb6bd 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4352,7 +4352,8 @@ public static class DefinitionNames public const string REDEEMABLE_SPARK_DLC0 = "def_redeemable_spark_dlc0"; public const string REDEEMABLE_SPARK_DLC1 = "def_redeemable_spark_dlc1"; public const string RELIC = "def_relic"; - public const string RESEARCH_KIT_0 = "def_research_kit_0"; + 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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index eaab92d04..d58b81a7a 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -304,12 +304,18 @@ - + + + + + - + + + diff --git a/src/Perpetuum/Services/Relics/RelicInfo.cs b/src/Perpetuum/Services/Relics/RelicInfo.cs index d87d3cb71..64cb8e1a4 100644 --- a/src/Perpetuum/Services/Relics/RelicInfo.cs +++ b/src/Perpetuum/Services/Relics/RelicInfo.cs @@ -1,10 +1,7 @@ using Perpetuum.Data; -using System; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Perpetuum.Services.Relics { @@ -28,10 +25,19 @@ public static RelicInfo GetByIDFromDB(int id) .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; diff --git a/src/Perpetuum/Services/Relics/RelicManager.cs b/src/Perpetuum/Services/Relics/RelicManager.cs deleted file mode 100644 index a2bd9afbc..000000000 --- a/src/Perpetuum/Services/Relics/RelicManager.cs +++ /dev/null @@ -1,322 +0,0 @@ -using Perpetuum.Zones; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Perpetuum.Services.RiftSystem; -using Perpetuum.Services.Looting; -using System.Drawing; -using Perpetuum.ExportedTypes; -using Perpetuum.Log; -using Perpetuum.Players; -using Perpetuum.Data; -using Perpetuum.Zones.Beams; -using System.Threading; -using Perpetuum.Threading; -using Perpetuum.EntityFramework; -using Perpetuum.Units; -using Perpetuum.Items; - -namespace Perpetuum.Services.Relics -{ - public class RelicManager - { - //Constants - private const double ACTIVATION_RANGE = 3; //30m - private const double RESPAWN_PROXIMITY = 10.0 * ACTIVATION_RANGE; - private readonly TimeSpan RESPAWN_RANDOM_WINDOW = TimeSpan.FromHours(1); - private readonly TimeSpan THREAD_TIMEOUT = TimeSpan.FromSeconds(4); - - private IZone _zone; - private RiftSpawnPositionFinder _spawnPosFinder; - private ReaderWriterLockSlim _lock; - private Random _random; - - private int _max_relics = 0; - private IEnumerable _spawnInfos; - private List _relics; - - private readonly TimeSpan _respawnRate = TimeSpan.FromHours(1.5); - private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); - - //DB-accessing objects - private RelicZoneConfigRepository relicZoneConfigRepository; - private RelicSpawnInfoRepository relicSpawnInfoRepository; - private RelicLootGenerator relicLootGenerator; - private readonly IEntityServices _entityServices; - - //Timers for update - private TimeSpan _refreshElapsed; - private TimeSpan _respawnElapsed; - private TimeSpan _respawnRandomized; - - public RelicManager(IZone zone, IEntityServices entityServices) - { - _random = new Random(); - _lock = new ReaderWriterLockSlim(); - _relics = new List(); - _zone = zone; - _entityServices = entityServices; - _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(); - } - - private TimeSpan RollNextSpawnTime() - { - var randomFactor = _random.NextDouble() - 0.5; - var minutesToAdd = RESPAWN_RANDOM_WINDOW.TotalMinutes * randomFactor; - - return _respawnRate.Add(TimeSpan.FromMinutes(minutesToAdd)); - } - - private int GetRelicCount() - { - using (_lock.Read(THREAD_TIMEOUT)) - return _relics.Count; - } - - public void Start() - { - //Inject max relics on first start - while (GetRelicCount() < _max_relics) - { - SpawnRelic(); - } - } - - public void Stop() - { - //TODO cleanup if using DB to cache relics - } - - 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 _relics.Select(r => r.ToDebugDictionary()).ToList(); - } - - private 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 - } - - private 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; - } - - - 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)) - { - pt = _spawnPosFinder.FindSpawnPosition(); - attempts++; - if (attempts > maxAttempts) - { - Logger.Error("Could not get Position for next Relic on Zone: " + _zone.Id); - return; - } - } - AddRelicToZone(info, pt.ToPosition()); - } - - private void AddRelicToZone(RelicInfo info, Position position) - { - using (_lock.Write(THREAD_TIMEOUT)) - { - var r = Relic.BuildAndAddToZone(info, _zone, position, relicLootGenerator.GenerateLoot(info)); - 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 RefreshBeam(Relic 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); - } - } - - private void UpdateRelics() - { - using (_lock.Write(THREAD_TIMEOUT)) - { - foreach (Relic r in _relics) - { - if (!r.IsAlive()) - { - r.RemoveFromZone(); - } - } - _relics.RemoveAll(r => !r.IsAlive()); - } - using (_lock.Read(THREAD_TIMEOUT)) - { - foreach (Relic r in _relics) - { - RefreshBeam(r); - } - } - } - - public 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; - } - - } - } -} 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..16ae717f3 --- /dev/null +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -0,0 +1,100 @@ +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 Outpost _outpost; + private Random _random; + private RelicInfo _sapRelicInfo; + + //Beam Draw refresh + private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); + + 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) + { + var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(90, 350); + var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); + posFinder.Find(out Position p); + return p; + } + + 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 index 939a8ba8d..dacb4e5ce 100644 --- a/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs +++ b/src/Perpetuum/Services/Relics/RelicSpawnInfo.cs @@ -13,12 +13,10 @@ namespace Perpetuum.Services.Relics public class RelicSpawnInfoRepository { private RelicSpawnInfoReader _relicSpawnInfoReader; - private IZone _zone; public RelicSpawnInfoRepository(IZone zone) { _relicSpawnInfoReader = new RelicSpawnInfoReader(zone); - _zone = zone; } public IEnumerable GetAll() diff --git a/src/Perpetuum/Services/Relics/Relic.cs b/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs similarity index 85% rename from src/Perpetuum/Services/Relics/Relic.cs rename to src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs index e6adc4fd8..f7a9632f9 100644 --- a/src/Perpetuum/Services/Relics/Relic.cs +++ b/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs @@ -13,16 +13,15 @@ namespace Perpetuum.Services.Relics { - public class Relic : Unit + public class AbstractRelic : Unit, IRelic { - private RelicInfo _info; - private IZone _zone; - private bool _alive; + protected RelicInfo _info; + protected IZone _zone; + protected bool _alive; - private const double ACTIVATION_RANGE = 3; //30m - private const double RESPAWN_PROXIMITY = 10.0 * ACTIVATION_RANGE; - private readonly TimeSpan MAXLIFESPAN = TimeSpan.FromDays(3); - private TimeSpan lifespan = TimeSpan.Zero; + protected const double ACTIVATION_RANGE = 3; //30m + protected readonly TimeSpan MAXLIFESPAN = TimeSpan.FromDays(3); + protected TimeSpan lifespan = TimeSpan.Zero; // Locks - Be sure to use locks independently and do not nest calls into other locks! // Lock for Live/Dead state of Relic @@ -31,12 +30,12 @@ public class Relic : Unit private ReaderWriterLockSlim _lifespanLock; private readonly TimeSpan THREAD_TIMEOUT = TimeSpan.FromSeconds(4); - private RelicLootItems _loots; + protected RelicLootItems _loots; [CanBeNull] - public static Relic BuildAndAddToZone(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems) + public static IRelic BuildAndAddToZone(RelicInfo info, IZone zone, Position position, RelicLootItems lootItems) { - var relic = (Relic)CreateUnitWithRandomEID(DefinitionNames.RELIC); + var relic = (AbstractRelic)CreateUnitWithRandomEID(DefinitionNames.RELIC); if (relic == null) return null; relic.Init(info, zone, position, lootItems); @@ -114,6 +113,11 @@ protected override void OnRemovedFromZone(IZone zone) base.OnRemovedFromZone(zone); } + public void RemoveFromZone() + { + base.RemoveFromZone(); + } + protected internal override void UpdatePlayerVisibility(Player player) { @@ -123,7 +127,7 @@ protected internal override void UpdatePlayerVisibility(Player player) } } - private void PopRelic(Player player) + public virtual void PopRelic(Player player) { //Set flag on relic for removal SetAlive(false); 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..287ee3475 --- /dev/null +++ b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs @@ -0,0 +1,37 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.Intrusion; +using System.Collections.Generic; + +namespace Perpetuum.Services.Relics +{ + public class SAPRelic : AbstractRelic + { + private Outpost _outpost; + + [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) + { + _outpost.PublishSAPEvent(new StabilityAffectingEvent(_outpost, player, this.Definition, this.Eid, 1)); + base.PopRelic(player); + } + } +} diff --git a/src/Perpetuum/Zones/IZone.cs b/src/Perpetuum/Zones/IZone.cs index 0a1dce1bb..91994ed91 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -48,7 +48,7 @@ public interface IZone PBSHighwayHandler HighwayHandler { get; } TerraformHandler TerraformHandler { get; } MiningLogHandler MiningLogHandler { get; } - RelicManager RelicManager { get; } + IRelicManager RelicManager { get; } IZoneUnitService UnitService { get; } IZoneEnterQueueService EnterQueueService { get; } diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 25af0a7ed..113f7a76b 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -18,6 +18,7 @@ using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.Looting; +using Perpetuum.Services.Relics; using Perpetuum.Timers; using Perpetuum.Units.DockingBases; using Perpetuum.Zones.Effects; @@ -39,7 +40,7 @@ public class Outpost : DockingBase private readonly ILootService _lootService; private static readonly ILookup _sapInfos; private readonly EventListenerService _eventChannel; - private OutpostDecay _decay; + private readonly OutpostDecay _decay; public static StabilityBonusThreshold[] StabilityBonusThresholds { get; private set; } public static int DefenseNodesStabilityLimit { get; private set; } @@ -78,6 +79,11 @@ public Outpost(IEntityServices entityServices, _decay = new OutpostDecay(_eventChannel, this); } + public void PublishSAPEvent(StabilityAffectingEvent eventMsg) + { + _eventChannel.PublishMessage(eventMsg); + } + public TimeRange IntrusionWaitTime { get; set; } public override void AcceptVisitor(IEntityVisitor visitor) diff --git a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs index c0e622410..c5c90a448 100644 --- a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs +++ b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs @@ -12,7 +12,7 @@ public class OutpostDecay { private readonly Outpost _outpost; private readonly EventListenerService _eventChannel; - private readonly static TimeSpan noDecayBefore = TimeSpan.FromDays(7); + 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; diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index ce1ab018d..a553c2f87 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -70,7 +70,7 @@ public abstract class Zone : Threading.Process.Process,IZone public RiftManager RiftManager { private get; set; } [CanBeNull] - public RelicManager RelicManager { get; set; } + public IRelicManager RelicManager { get; set; } public IZoneEnterQueueService EnterQueueService { get; set; } From e1069437459c5ac0d3b6cc1205e67bbe18643de9 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 26 Oct 2019 18:05:28 -0400 Subject: [PATCH 117/457] Specimen SAP param changes and Outpost Max threshold. (#113) * speciment sap balance and final outpost threshold * allow repeat item(s) --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 2 +- .../Zones/Intrusion/SpecimenProcessingSAP.cs | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 113f7a76b..0f2d5d81c 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -28,7 +28,7 @@ namespace Perpetuum.Zones.Intrusion public class Outpost : DockingBase { private const int EP_WINNER = 120; - private const int MAX_STABILITY = 120; + 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; 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; } From 455fff9c87d96416ab024c516656155f351efdc9 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 2 Nov 2019 15:07:02 -0400 Subject: [PATCH 118/457] Flagging on SAP relics, and npcs (#114) * flag on npc boss w/ outpost (guardians) + SAPrelic pvp flag --- src/Perpetuum/Services/Relics/Relics/SAPRelic.cs | 2 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 15 +++++++++++++-- src/Perpetuum/Zones/NpcSystem/Npc.cs | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs index 287ee3475..4d95a4661 100644 --- a/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs +++ b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs @@ -3,7 +3,6 @@ using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Zones; using Perpetuum.Zones.Intrusion; -using System.Collections.Generic; namespace Perpetuum.Services.Relics { @@ -30,6 +29,7 @@ private void SetOutpost(Outpost outpost) public override void PopRelic(Player player) { + player.ApplyPvPEffect(); _outpost.PublishSAPEvent(new StabilityAffectingEvent(_outpost, player, this.Definition, this.Eid, 1)); base.PopRelic(player); } diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index a41b6d475..e45d4253c 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -1,4 +1,5 @@ using Perpetuum.Data; +using Perpetuum.Players; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Units; @@ -53,6 +54,7 @@ public static NpcBossInfo GetBossInfoByFlockID(int flockid) private readonly bool _overrideRelations; private readonly string _deathMsg; private readonly string _aggroMsg; + private bool _sayOnce; public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg) { @@ -65,6 +67,7 @@ public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSpl _overrideRelations = overrideRelations; _deathMsg = customDeathMsg; _aggroMsg = customAggroMsg; + _sayOnce = true; } // TODO - find good place to call this that will be stable and not noisey (excessive repeat messages) @@ -73,9 +76,17 @@ public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSpl /// /// Player aggressor /// the npc event channel - public void OnAggro(Unit aggressor, EventListenerService channel) + public void OnAggro(Player aggressor, EventListenerService channel) { - CommunicateAggression(aggressor, channel); + if (_sayOnce) + { + _sayOnce = false; + CommunicateAggression(aggressor, channel); + } + if (_outpostEID != null) + { + aggressor.ApplyPvPEffect(); + } } /// diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index d9d0605c2..ec6b3a54e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -784,6 +784,11 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) return; AddThreat(player,new Threat(ThreatType.Damage, e.TotalDamage * 0.9),true); + + if (IsBoss()) + { + BossInfo.OnAggro(player, _eventChannel); + } } protected override void OnDead(Unit killer) From 848ce8754520f91d674e3da3777443f419f81edd Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 3 Nov 2019 00:49:18 -0400 Subject: [PATCH 119/457] Fix Outpost takeover by non-player corp(#115) --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 0f2d5d81c..7735925d0 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -446,7 +446,7 @@ private void processStabilityChange(StabilityAffectingEvent sap) } } - if (siteInfo.Owner == null) + if (siteInfo.Owner == null && winnerCorporation is PrivateCorporation) { // No owner - winner gets outpost, for any SAP event logEvent.EventType = IntrusionEvents.siteOwnershipGain; From 24f34e4bb72f95fe1c6a27645a18bcf0f97871ad Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 3 Nov 2019 12:23:23 -0500 Subject: [PATCH 120/457] fix npc corp players not affecting stab on sap npcs (#116) --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 7735925d0..96fe03695 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -413,7 +413,7 @@ private void processStabilityChange(StabilityAffectingEvent sap) OldStability = oldStability }; - if (sap.IsSystemGenerated()) + if (sap.IsSystemGenerated() || sap.OverrideRelations) { newStability = (newStability + sap.StabilityChange); } @@ -432,7 +432,7 @@ private void processStabilityChange(StabilityAffectingEvent sap) 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 || sap.OverrideRelations) + if (winnerCorporation.Eid == siteInfo.Owner) { newStability = (newStability + sap.StabilityChange); } From e5e720708a7afa6b6ab97dd4c7e8333f4243394b Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 3 Nov 2019 12:46:42 -0500 Subject: [PATCH 121/457] fix 3 (#117) --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 96fe03695..07617844a 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -446,12 +446,20 @@ private void processStabilityChange(StabilityAffectingEvent sap) } } - if (siteInfo.Owner == null && winnerCorporation is PrivateCorporation) + if (siteInfo.Owner == null) { - // No owner - winner gets outpost, for any SAP event - logEvent.EventType = IntrusionEvents.siteOwnershipGain; - newOwner = winnerCorporation.Eid; - newStability = STARTING_STABILITY; + if (winnerCorporation is PrivateCorporation) + { + // No owner - winner gets outpost, for any SAP event + logEvent.EventType = IntrusionEvents.siteOwnershipGain; + newOwner = winnerCorporation.Eid; + newStability = STARTING_STABILITY; + } + else + { + // No owner - winner is non-player corp => stability stays at 0 + newStability = MIN_STABILITY; + } } else if (newStability <= 0) { From 6df239fa2a4649b539806a73421c5d0fad448047 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 4 Nov 2019 22:17:56 -0500 Subject: [PATCH 122/457] isattackable does not depend on if player is targetting npc (#118) --- src/Perpetuum/Zones/NpcSystem/Npc.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index ec6b3a54e..8ab3f92d0 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -169,11 +169,6 @@ private bool IsAttackable(Hostile hostile) if (hostile.unit.IsInvulnerable) return false; - if (hostile.unit is Player player) - { - return player.GetLocks().OfType().Where(x => x.Target.Eid == npc.Eid).Any(); - } - if (npc.Behavior.Type == NpcBehaviorType.Neutral) { if (hostile.IsExpired) From 7afa1c7915202e8749cf9889f5e13b7efeacc04a Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Dec 2019 10:26:24 -0500 Subject: [PATCH 123/457] Lootsplit fix (npc bosses) (#119) --- src/Perpetuum/Services/Looting/LootGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index c6f92a0ed..90ca9030b 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -68,7 +68,7 @@ public List GetGenerators(int splitCount) amounts = amounts.OrderBy(x => _random.Next()).ToArray(); for (var i = 0; i < splitCount; i++) { - ItemInfo itemInfo = new ItemInfo(lootItem.ItemInfo.Definition, splitQuantity, splitQuantity); + ItemInfo itemInfo = new ItemInfo(lootItem.ItemInfo.Definition, amounts[i], amounts[i]); var generatorInfo = new LootGeneratorItemInfo(itemInfo, info.damaged, info.probability); generatorInfos.Add(generatorInfo); } From 3d96566ab25beea09b2c6f949e05f08a17690290 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Dec 2019 10:26:51 -0500 Subject: [PATCH 124/457] Decay reset on owner any owner SAE activity & on new ownership (#120) * reset decay when the owner does any SAE * reset decay on initial takeover by new owner corp --- src/Perpetuum/Zones/Intrusion/Outpost.cs | 10 ++++++++-- src/Perpetuum/Zones/Intrusion/OutpostDecay.cs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 07617844a..6970040ed 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -34,7 +34,6 @@ public class Outpost : DockingBase private const int PRODUCTION_BONUS_THRESHOLD = 100; private TimeRange _intrusionWaitTime => IntrusionWaitTime; - private TimeSpan _timeSinceLastSAP = TimeSpan.Zero; private readonly IEntityServices _entityServices; private readonly ICorporationManager _corporationManager; private readonly ILootService _lootService; @@ -331,7 +330,7 @@ public bool Enabled private void OnSAPTakeOver(SAP sap) { - _decay.OnSAP(); + Task.Run(() => HandleTakeOver(sap)).ContinueWith(t => IntrusionInProgress = false); } @@ -403,6 +402,12 @@ private void processStabilityChange(StabilityAffectingEvent sap) var newOwner = siteInfo.Owner; var oldOwner = siteInfo.Owner; + // Reset Decay timer on any StabilityAffectingEvent completed by the owner + if (winnerCorporation.Eid == siteInfo.Owner) + { + _decay.ResetDecayTimer(); + } + var logEvent = new IntrusionLogEvent { OldOwner = siteInfo.Owner, @@ -454,6 +459,7 @@ private void processStabilityChange(StabilityAffectingEvent sap) logEvent.EventType = IntrusionEvents.siteOwnershipGain; newOwner = winnerCorporation.Eid; newStability = STARTING_STABILITY; + _decay.ResetDecayTimer(); } else { diff --git a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs index c5c90a448..4bab42444 100644 --- a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs +++ b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs @@ -42,7 +42,7 @@ public void OnUpdate(TimeSpan time) /// /// Called when a SAP is completed /// - public void OnSAP() + public void ResetDecayTimer() { lastSuccessfulIntrusion = TimeSpan.Zero; } From a884fff988d06286f789b6ca922c79c80d8167c7 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Dec 2019 10:27:33 -0500 Subject: [PATCH 125/457] NPC Boss onAggro behaviour fix (#121) --- src/Perpetuum/Zones/NpcSystem/Npc.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 8ab3f92d0..13eae3436 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -668,6 +668,10 @@ public Player GetTagger() public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) { + if (IsBoss() && hostile.IsPlayer()) + { + BossInfo.OnAggro(hostile as Player, _eventChannel); + } _threatManager.GetOrAddHostile(hostile).AddThreat(threat); var PseudoThreat = PseudoThreats.Where(x => x.Unit == hostile).FirstOrDefault(); @@ -778,12 +782,7 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) if (player == null) return; - AddThreat(player,new Threat(ThreatType.Damage, e.TotalDamage * 0.9),true); - - if (IsBoss()) - { - BossInfo.OnAggro(player, _eventChannel); - } + AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); } protected override void OnDead(Unit killer) @@ -1011,7 +1010,7 @@ protected override void OnUnitLockStateChanged(Lock @lock) return; var threatValue = unitLock.Primary ? Threat.LOCK_PRIMARY : Threat.LOCK_SECONDARY; - AddThreat(unitLock.Owner,new Threat(ThreatType.Lock, threatValue),true); + AddThreat(unitLock.Owner, new Threat(ThreatType.Lock, threatValue), true); } protected override void OnUnitTileChanged(Unit target) From b03a31349378290e02d57216823b7eeccd13fe59 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Dec 2019 10:27:52 -0500 Subject: [PATCH 126/457] SAP bug: handle dead players (#123) * handle dead players --- .../EventServices/EventMessages/StabilityEffectMessage.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs index 20c60a3b7..3f4241486 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs @@ -11,7 +11,7 @@ namespace Perpetuum.Services.EventServices.EventMessages public class StabilityAffectingEvent : EventMessage { private Player _player; - private IList _particpants = new List(); + private List _participants = new List(); public Outpost Outpost { get; } public bool OverrideRelations { get; } public int StabilityChange { get; } @@ -27,7 +27,7 @@ public StabilityAffectingEvent(Outpost outpost, Player winner, int def, long? ei Eid = eid; if (participants != null) { - _particpants = participants; + _participants = participants as List; } OverrideRelations = overrideRelations; @@ -40,7 +40,8 @@ public bool IsSystemGenerated() public IList GetPlayers() { - return _particpants; + _participants.RemoveAll(p => p == null); + return _participants; } [CanBeNull] From a6b5b56d3a5c9ff6c3addc1b6f624553271e5af0 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Dec 2019 10:28:18 -0500 Subject: [PATCH 127/457] Mission Payout tweak (#124) --- .../MissionTargets/MissionTargetRewardCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index 4dfa2435c..2bfbde642 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -68,7 +68,7 @@ private void PayAsMineral() } //ECONOMY! TODO put this in DB! - public static readonly double[] PayOutMultipliers = new double[] { 50, 75, 125, 250, 450, 600, 700, 800, 900, 1000 }; + public static readonly double[] PayOutMultipliers = new double[] { 75, 125, 200, 300, 450, 600, 700, 800, 900, 1000 }; private void PayReward() { From 967fc3763f212c7d262dea7dd9a8a898ee87b8b7 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Dec 2019 12:48:31 -0500 Subject: [PATCH 128/457] add support for custom resource server URLs (#125) --- src/Perpetuum/GlobalConfiguration.cs | 2 ++ src/Perpetuum/Services/Sessions/SessionManager.cs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/Perpetuum/GlobalConfiguration.cs b/src/Perpetuum/GlobalConfiguration.cs index b79bb32fa..7cb14150e 100644 --- a/src/Perpetuum/GlobalConfiguration.cs +++ b/src/Perpetuum/GlobalConfiguration.cs @@ -16,6 +16,8 @@ public class GlobalConfiguration public int SteamAppID { get; set; } public byte[] SteamKey { get; set; } + public string ResourceServerURL { get; set; } + public CorporationConfiguration Corporation { get; set; } } } \ No newline at end of file diff --git a/src/Perpetuum/Services/Sessions/SessionManager.cs b/src/Perpetuum/Services/Sessions/SessionManager.cs index 6a73d9d4e..8872b2935 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,6 +31,7 @@ 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; @@ -82,6 +84,11 @@ private void OnSessionRsaKeyReceived(ISession session) {"steamLoginEnabled",_steamManager.SteamAppID > 0} }; + if (!string.IsNullOrEmpty(_globalConfiguration.ResourceServerURL)) + { + welcomeData.Add("resourceServerURL", _globalConfiguration.ResourceServerURL); + } + var builder = Message.Builder.SetCommand(Commands.Welcome).WithData(welcomeData); session.SendMessage(builder); } From dcaaca1736fc790d4f6b5c555138aa8139ed2a1f Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 21 Dec 2019 22:25:19 -0500 Subject: [PATCH 129/457] pushplayer fix --- src/Perpetuum/Zones/PBS/PBSHelper.cs | 40 +++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 6802915bd..826b8ca69 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -234,14 +234,25 @@ public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, E } - 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; @@ -249,6 +260,29 @@ private static void PushPlayersFromPosition(IZone zone, Position position, int r } } + /// + /// 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); From a7aaf06ba7cfd1a6b0f4e5c94de8f8ad73b266ef Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sat, 18 Jan 2020 14:26:53 -0500 Subject: [PATCH 130/457] First attempt to fix the spawn position Current bug is that the server is kept on an infinite spawn loot The reason is that the behavior of the spawn is changed and now returning an invalid location if it was not possible to find a valid location --- .../RelicManagers/OutpostRelicManager.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index 16ae717f3..8be2d54ff 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -7,6 +7,10 @@ using Perpetuum.Zones.Intrusion; using Perpetuum.Zones.Finders.PositionFinders; using System.Threading; +using Perpetuum.PathFinders; +using Perpetuum.Accounting.Characters; +using Perpetuum.Units; +using Perpetuum.Log; namespace Perpetuum.Services.Relics { @@ -72,9 +76,44 @@ protected override RelicInfo GetNextRelicType() protected override Point FindRelicPosition(RelicInfo info) { - var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(90, 350); - var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); - posFinder.Find(out Position p); + Position p = new Position(); + bool passableHandler(int x, int y) + { + return Zone.IsWalkable(new Point(x, y)); + } + PathFinder pathFinder = new AStarFinder(Heuristic.None, passableHandler); + Position outpostPosition = _outpost.CurrentPosition; + Position invalidPoint = new Position(0, 0); + + Point[] result = null; + var attemptCount = 0; + for(attemptCount = 0; attemptCount < 10; attemptCount++) + { + var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(90, 350); + var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); + + var foundValidLocation = posFinder.Find(out p); + + if (!foundValidLocation) + { + Logger.Info("Invalid location!"); + return p; + } + + Logger.Info("Looking for a relic"); + result = pathFinder.FindPath(outpostPosition, p); + if(result != null) + { + break; + } + } + + if (result == null) + { + Logger.Info("Invalid location!"); + p = invalidPoint; + } + return p; } From e356c0e3268ccea7305b89490073c6f58d0f6390 Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sat, 18 Jan 2020 15:13:36 -0500 Subject: [PATCH 131/457] Update spawn logic to check walkable area for a reasonable size to make sure it does not spawn on invalid location --- .../RelicManagers/OutpostRelicManager.cs | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index 8be2d54ff..d7786216f 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -77,31 +77,17 @@ protected override RelicInfo GetNextRelicType() protected override Point FindRelicPosition(RelicInfo info) { Position p = new Position(); - bool passableHandler(int x, int y) - { - return Zone.IsWalkable(new Point(x, y)); - } - PathFinder pathFinder = new AStarFinder(Heuristic.None, passableHandler); - Position outpostPosition = _outpost.CurrentPosition; Position invalidPoint = new Position(0, 0); - Point[] result = null; + List result = null; var attemptCount = 0; for(attemptCount = 0; attemptCount < 10; attemptCount++) { var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(90, 350); var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); - var foundValidLocation = posFinder.Find(out p); - - if (!foundValidLocation) - { - Logger.Info("Invalid location!"); - return p; - } - - Logger.Info("Looking for a relic"); - result = pathFinder.FindPath(outpostPosition, p); + posFinder.Find(out p); + result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), 200); if(result != null) { break; From 2f2b59265a15adec6d16225a8f5aa60f603768fb Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sat, 18 Jan 2020 15:35:32 -0500 Subject: [PATCH 132/457] Put hardcoded values as constants Cleaning up the find position method --- .../Relics/RelicManagers/OutpostRelicManager.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index d7786216f..64877e90c 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -27,6 +27,11 @@ public class OutpostRelicManager : AbstractRelicManager //Beam Draw refresh private readonly TimeSpan _relicRefreshRate = TimeSpan.FromSeconds(19.95); + // Spawn range and area + private readonly int SPAWN_MINIMUM_OUTPOST_DISTANCE = 90; + private readonly int SPAWN_MAXIMUM_OUTPOST_DISTANCE = 350; + private readonly int SPAWN_AREA_REQUIRED_SIZE = 200; + private IZone _zone; protected override IZone Zone { @@ -77,17 +82,16 @@ protected override RelicInfo GetNextRelicType() protected override Point FindRelicPosition(RelicInfo info) { Position p = new Position(); - Position invalidPoint = new Position(0, 0); + Position invalidPosition = new Position(0, 0); List result = null; - var attemptCount = 0; - for(attemptCount = 0; attemptCount < 10; attemptCount++) + for(int i = 0; i < 10; i++) { - var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(90, 350); + var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(SPAWN_MINIMUM_OUTPOST_DISTANCE, SPAWN_MAXIMUM_OUTPOST_DISTANCE); var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); posFinder.Find(out p); - result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), 200); + result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); if(result != null) { break; @@ -97,7 +101,7 @@ protected override Point FindRelicPosition(RelicInfo info) if (result == null) { Logger.Info("Invalid location!"); - p = invalidPoint; + p = invalidPosition; } return p; From 03b4a6b3aa5cc62c4cb364b31b184cde72aa22f3 Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sat, 18 Jan 2020 15:37:00 -0500 Subject: [PATCH 133/457] Move line that was too long --- .../Services/Relics/RelicManagers/OutpostRelicManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index 64877e90c..3f02c4f73 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -87,7 +87,8 @@ protected override Point FindRelicPosition(RelicInfo info) List result = null; for(int i = 0; i < 10; i++) { - var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D(SPAWN_MINIMUM_OUTPOST_DISTANCE, SPAWN_MAXIMUM_OUTPOST_DISTANCE); + var randomPos = _outpost.CurrentPosition.GetRandomPositionInRange2D( + SPAWN_MINIMUM_OUTPOST_DISTANCE, SPAWN_MAXIMUM_OUTPOST_DISTANCE); var posFinder = new ClosestWalkablePositionFinder(_zone, randomPos); posFinder.Find(out p); From f908a1ace0421d3f42833eefe83b46f20a8293ab Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sat, 18 Jan 2020 15:37:46 -0500 Subject: [PATCH 134/457] Remove test log --- .../Services/Relics/RelicManagers/OutpostRelicManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index 3f02c4f73..24b1d7e1e 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -101,7 +101,6 @@ protected override Point FindRelicPosition(RelicInfo info) if (result == null) { - Logger.Info("Invalid location!"); p = invalidPosition; } From b171585970af4f8f9eb5944cf75982ee4bcaa9a1 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 19 Jan 2020 15:26:56 -0500 Subject: [PATCH 135/457] pushplayer fix (#128) --- src/Perpetuum/Zones/PBS/PBSHelper.cs | 40 +++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 6802915bd..826b8ca69 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -234,14 +234,25 @@ public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, E } - 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; @@ -249,6 +260,29 @@ private static void PushPlayersFromPosition(IZone zone, Position position, int r } } + /// + /// 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); From 024688a68d28f034ee31c9db23b774fbe3e78ce7 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 19 Jan 2020 15:28:09 -0500 Subject: [PATCH 136/457] pushplayer fix (#128) From 5d4f2a95b2d5488a43575c74b977edf2ec919382 Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Tue, 21 Jan 2020 21:59:28 -0500 Subject: [PATCH 137/457] Remove unused imports --- .../Services/Relics/RelicManagers/OutpostRelicManager.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index 24b1d7e1e..66499df50 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -7,10 +7,6 @@ using Perpetuum.Zones.Intrusion; using Perpetuum.Zones.Finders.PositionFinders; using System.Threading; -using Perpetuum.PathFinders; -using Perpetuum.Accounting.Characters; -using Perpetuum.Units; -using Perpetuum.Log; namespace Perpetuum.Services.Relics { From 51eeb3f2a518ef1712dcee9de342074474e9fb94 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 15 Feb 2020 23:38:47 -0500 Subject: [PATCH 138/457] destro bug, needs to pick winning corp not winning player, builder refactor --- .../EventMessages/StabilityEffectMessage.cs | 109 ++++++++++++++---- .../Services/Relics/Relics/SAPRelic.cs | 9 +- src/Perpetuum/Zones/Intrusion/Outpost.cs | 8 +- src/Perpetuum/Zones/Intrusion/OutpostDecay.cs | 11 +- src/Perpetuum/Zones/Intrusion/SAP.cs | 12 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 17 +-- 6 files changed, 123 insertions(+), 43 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs index 3f4241486..b6c00cefc 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs @@ -10,38 +10,34 @@ namespace Perpetuum.Services.EventServices.EventMessages /// public class StabilityAffectingEvent : EventMessage { - private Player _player; + 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 Outpost Outpost { get; } - public bool OverrideRelations { get; } - public int StabilityChange { get; } - public int Definition { get; } - public long? Eid { get; } - - public StabilityAffectingEvent(Outpost outpost, Player winner, int def, long? eid, int sapPoints, IList participants = null, bool overrideRelations = false) + public List Participants { - Outpost = outpost; - _player = winner; - StabilityChange = sapPoints; - Definition = def; - Eid = eid; - if (participants != null) + get + { + _participants.RemoveAll(p => p == null); + return _participants; + } + private set { - _participants = participants as List; + _participants = value; } - - OverrideRelations = overrideRelations; } - public bool IsSystemGenerated() + public static StabilityAffectBuilder Builder() { - return _player == null; + return new StabilityAffectBuilder(); } - public IList GetPlayers() + public bool IsSystemGenerated() { - _participants.RemoveAll(p => p == null); - return _participants; + return Winner == null; } [CanBeNull] @@ -49,7 +45,74 @@ public Corporation GetWinnerCorporation() { if (IsSystemGenerated()) return Corporation.GetByName("syndicate_police_central"); - return Corporation.Get(_player.CorporationEid); + 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/Relics/Relics/SAPRelic.cs b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs index 4d95a4661..3f66c8aed 100644 --- a/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs +++ b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs @@ -29,8 +29,15 @@ private void SetOutpost(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(new StabilityAffectingEvent(_outpost, player, this.Definition, this.Eid, 1)); + _outpost.PublishSAPEvent(builder.Build()); base.PopRelic(player); } } diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 6970040ed..595ae891b 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -347,7 +347,7 @@ private void HandleTakeOver(SAP sap) { var gen = new LootGenerator(_lootService.GetIntrusionLootInfos(this, sap)); LootContainer.Create().AddLoot(gen).BuildAndAddToZone(Zone, sap.CurrentPosition); - processStabilityChange(sap.toStabilityAffectingEvent()); + ProcessStabilityChange(sap.ToStabilityAffectingEvent()); scope.Complete(); } catch (Exception ex) @@ -370,7 +370,7 @@ public void IntrusionEvent(StabilityAffectingEvent sap) { try { - processStabilityChange(sap); + ProcessStabilityChange(sap); scope.Complete(); } catch (Exception ex) @@ -385,7 +385,7 @@ public void IntrusionEvent(StabilityAffectingEvent sap) /// /// Core SAP logic /// - private void processStabilityChange(StabilityAffectingEvent sap) + private void ProcessStabilityChange(StabilityAffectingEvent sap) { // Check for invalid player-SAPS var winnerCorporation = sap.GetWinnerCorporation(); @@ -484,7 +484,7 @@ private void processStabilityChange(StabilityAffectingEvent sap) InsertIntrusionLog(logEvent); //Award EP - foreach (var player in sap.GetPlayers()) + foreach (var player in sap.Participants) { player.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Intrusion, EP_WINNER); } diff --git a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs index 4bab42444..7382052b8 100644 --- a/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs +++ b/src/Perpetuum/Zones/Intrusion/OutpostDecay.cs @@ -10,19 +10,22 @@ namespace Perpetuum.Zones.Intrusion /// public class OutpostDecay { - private readonly Outpost _outpost; 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 readonly EntityDefault def = EntityDefault.GetByName("def_outpost_decay"); + private StabilityAffectingEvent.StabilityAffectBuilder _builder; public OutpostDecay(EventListenerService eventChannel, Outpost outpost) { - _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) @@ -49,7 +52,7 @@ public void ResetDecayTimer() private void DoDecay() { - _eventChannel.PublishMessage(new StabilityAffectingEvent(_outpost, null, def.Definition, null, decayPts)); + _eventChannel.PublishMessage(_builder.Build()); } } } diff --git a/src/Perpetuum/Zones/Intrusion/SAP.cs b/src/Perpetuum/Zones/Intrusion/SAP.cs index 590e19876..2d18f3702 100644 --- a/src/Perpetuum/Zones/Intrusion/SAP.cs +++ b/src/Perpetuum/Zones/Intrusion/SAP.cs @@ -327,15 +327,21 @@ private IEnumerable GetCorporationScores() /// Adapter method for SAP->StabilityAffectingEvent /// /// StabilityAffectingEvent - public StabilityAffectingEvent toStabilityAffectingEvent() + public StabilityAffectingEvent ToStabilityAffectingEvent() { List players = new List(); foreach(var player in this.PlayerInfos) { players.Add(player.character.GetPlayerRobotFromZone()); } - var winner = GetPlayerTopScores(1)[0].character.GetPlayerRobotFromZone(); - return new StabilityAffectingEvent(Site, winner, Definition, Eid, StabilityChange, players); + 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/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index e45d4253c..672d8d019 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -107,14 +107,15 @@ public void OnDeath(Npc npc, Unit killer, EventListenerService channel) if (outpost is Outpost) { var participants = npc.ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)).ToList(); - EventMessage sapMessage = new StabilityAffectingEvent(outpost as Outpost, - zone.ToPlayerOrGetOwnerPlayer(killer), - npc.Definition, - npc.Eid, - StabilityPoints(), - participants, - OverrideRelations()); - channel.PublishMessage(sapMessage); + 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); + channel.PublishMessage(builder.Build()); } } } From 76a530c03b4bedd4990c875ed375e9491ec803bb Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 10 Mar 2020 22:53:21 -0400 Subject: [PATCH 139/457] iz npcs, bootstrapping bugged? --- .../PerpetuumBootstrapper.cs | 39 +++++++-- .../Zone/NPCCheckCondition.cs | 4 +- .../Zone/StatsMapDrawing/ZoneDrawStatMap.cs | 2 +- src/Perpetuum/Perpetuum.csproj | 8 ++ src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 2 +- .../Zones/NpcSystem/Flocks/NormalFlock.cs | 2 +- .../NpcSystem/Presences/DirectPresence.cs | 2 +- .../Presences/DynamicPoolPresence.cs | 4 +- .../NpcSystem/Presences/DynamicPresence.cs | 6 +- .../Presences/IPresenceConfiguration.cs | 30 +++++++ .../Presences/IPresenceConfigurationReader.cs | 4 +- .../IInterzonePresenceConfigurationReader.cs | 9 ++ .../IInterzonePresenceManager.cs | 8 ++ .../InterzonePresences/InterzoneGroup.cs | 26 ++++++ .../InterzonePresences/InterzonePresence.cs | 13 +++ .../InterzonePresenceConfigReader.cs | 65 +++++++++++++++ .../InterzonePresenceManager.cs | 82 +++++++++++++++++++ .../Zones/NpcSystem/Presences/Presence.cs | 22 ++--- .../Presences/PresenceConfiguration.cs | 50 +++++------ .../Presences/PresenceConfigurationReader.cs | 34 ++++---- .../NpcSystem/Presences/PresenceFactory.cs | 2 +- .../NpcSystem/Presences/PresenceManager.cs | 4 +- .../Zones/NpcSystem/Presences/PresenceType.cs | 3 +- .../NpcSystem/Presences/RandomPresence.cs | 6 +- .../NpcSystem/Presences/RoamingPresence.cs | 4 +- .../Zones/NpcSystem/Presences/TestService.cs | 32 ++++++++ 26 files changed, 382 insertions(+), 81 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index dc45b8991..942583581 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -126,6 +126,7 @@ using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.NpcSystem.Flocks; using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; using Perpetuum.Zones.PBS; @@ -260,7 +261,7 @@ public IEnumerable GetCommands() public void Init(string gameRoot) { _builder = new ContainerBuilder(); - InitContainer(gameRoot); + InitContainer(gameRoot); _container = _builder.Build(); Logger.Current = _container.Resolve>(); @@ -310,7 +311,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()); @@ -1439,6 +1439,8 @@ public void RegisterNpcs() _builder.RegisterType().As(); _builder.RegisterType().As(); + _builder.RegisterType().As(); + _builder.RegisterType(); _builder.RegisterType().OnActivated(e => { var pm = e.Context.Resolve(); @@ -1465,7 +1467,7 @@ public void RegisterNpcs() 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)); }); }); @@ -1487,20 +1489,21 @@ public void RegisterNpcs() RegisterPresence(PresenceType.Random); RegisterPresence(PresenceType.Roaming); RegisterPresence(PresenceType.FreeRoaming); + RegisterPresence(PresenceType.Interzone); _builder.Register(x => { var 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)); + var p = ctx.ResolveKeyed(configuration.PresenceType,TypedParameter.From(zone),TypedParameter.From(configuration)); if (p is RoamingPresence roamingPresence) { - switch (p.Configuration.presenceType) + switch (p.Configuration.PresenceType) { case PresenceType.Roaming: { @@ -1652,6 +1655,11 @@ private void InitRelayManager() _builder.RegisterType(); + _builder.RegisterType().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(5))); + }); + //TODO new EPBonusEventService _builder.RegisterType().SingleInstance().OnActivated(e => { @@ -1668,7 +1676,23 @@ private void InitRelayManager() e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); }); - + + //TODO new InterzoneNPCManager + //_builder.RegisterType().SingleInstance().OnActivated(e => + //{ + // Console.WriteLine("is this working?!"); + // try + // { + // e.Instance.Init(e.Context.Resolve(), e.Context.Resolve(), e.Context.Resolve()); + // var pm = e.Context.Resolve(); + // var timed = e.Instance.AsTimed(TimeSpan.FromSeconds(1)); + // pm.AddProcess(timed); + // } + // catch (Exception ex) + // { + // Console.WriteLine(ex.ToString()); + // } + //}); _builder.RegisterType().As(); @@ -2519,7 +2543,6 @@ void RegisterZone(ZoneType type) where T:Zone e.Instance.Zones.Add(zone); }; - }).SingleInstance(); _builder.RegisterType(); 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/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 9152f4c7b..46a1cce89 100644 --- a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs +++ b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs @@ -353,7 +353,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.Red, presence.Area.X1, presence.Area.Y1); + graphics.DrawString(presence.Configuration.Name, new Font("Tahoma", 8), Brushes.Red, presence.Area.X1, presence.Area.Y1); } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d58b81a7a..2c13f717b 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -675,6 +675,13 @@ + + + + + + + @@ -684,6 +691,7 @@ + diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index 2b0563820..ec484a369 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -135,7 +135,7 @@ protected virtual void CreateMemberInZone() 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}"); + Log($"invalid spawnposition in CreateMemberInZone: {spawnPosition} {Configuration.Name} {Presence.Configuration.Name} zone:{zone.Id}"); } OnNpcCreated(npc); diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index ad1b88168..45bff5d5b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs @@ -82,7 +82,7 @@ public override void Update(TimeSpan time) { _elapsedTime += time; - if (!Presence.Configuration.isRespawnAllowed) + if (!Presence.Configuration.IsRespawnAllowed) return; RespawnAllDeadNpcs(time); 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..1de611f5d 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)); } } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs index 39a1105d5..47c0b1bb6 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs @@ -19,10 +19,10 @@ public TimeSpan LifeTime public event Action PresenceExpired; - public DynamicPresence(IZone zone,PresenceConfiguration configuration) : base(zone,configuration) + public DynamicPresence(IZone zone, IPresenceConfiguration configuration) : base(zone,configuration) { - if (Configuration.dynamicLifeTime != null) - LifeTime = TimeSpan.FromMilliseconds((int) Configuration.dynamicLifeTime); + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromMilliseconds((int) Configuration.DynamicLifeTime); } public override Area Area diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs new file mode 100644 index 000000000..4551ffa7f --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs @@ -0,0 +1,30 @@ + +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 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..f735ebde5 --- /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/IInterzonePresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs new file mode 100644 index 000000000..3199e39b2 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs @@ -0,0 +1,8 @@ +using Perpetuum.Threading.Process; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public interface IInterzonePresenceManager : IProcess + { + } +} \ No newline at end of file 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..f8eb9ae72 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public class InterzoneGroup + { + public int id; + public string name; + private List presences = new List(); + + public void AddConfigs(IEnumerable presences) + { + this.presences.AddMany(presences); + } + + public List GetAll() + { + return this.presences; + } + + public IPresenceConfiguration GetRandom() + { + return presences.RandomElement(); + } + } +} 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..7da5ea64d --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -0,0 +1,13 @@ +using System; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public class InterzonePresence : DynamicPresence + { + public InterzonePresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) + { + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); + } + } +} \ 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..3ea207f3f --- /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 FROM npcinterzonegroups").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") + .Execute() + .Select(CreatePresenceConfigurationFromRecord).ToArray(); + + foreach (var group in groups) + { + var configs = presenceConfigs.Where(p => group.id == p.InterzoneGroupId); + group.AddConfigs(configs); + } + return groups; + } + + private static InterzoneGroup CreateIZGroupFromRecord(IDataRecord record) + { + var g = new InterzoneGroup() + { + id = record.GetValue("id"), + name = record.GetValue("name") + }; + 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..c8f92c029 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs @@ -0,0 +1,82 @@ +using Perpetuum.Log; +using Perpetuum.Threading.Process; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences +{ + public class InterzonePresenceManager : Process + { + + public delegate IInterzonePresenceManager Factory(IZoneManager zoneManager, PresenceFactory presenceFactory); + + private PresenceFactory _presenceFactory; + private IInterzonePresenceConfigurationReader _configurationReader; + private IEnumerable _groups = new List(); + private ImmutableList _presences = ImmutableList.Empty; + + public IZoneManager _zoneManager; + + public InterzonePresenceManager() + { + + } + + public void Init(IZoneManager zoneManager, PresenceFactory presenceFactory, IInterzonePresenceConfigurationReader configurationReader) + { + _presenceFactory = presenceFactory; + _configurationReader = configurationReader; + _zoneManager = zoneManager; + } + public override void Start() + { + base.Start(); + _groups = _configurationReader.GetAll(); + foreach(var group in _groups) + { + SpawnRandomPresenseOfGroup(group); + } + } + + public void SpawnRandomPresenseOfGroup(InterzoneGroup group) + { + var config = group.GetRandom(); + var zone = _zoneManager.GetZone(config.ZoneID); + var presence = _presenceFactory(zone, config); + + AddPresence(presence); + } + + public void AddPresence(Presence presence) + { + Logger.Info("Adding Presence: " + presence.Configuration.Name); + ImmutableInterlocked.Update(ref _presences, p => p.Add(presence)); + + if (presence is INotifyPresenceExpired notifier) + notifier.PresenceExpired += OnPresenceExpired; + } + + private void OnPresenceExpired(Presence presence) + { + ImmutableInterlocked.Update(ref _presences, p => p.Remove(presence)); + Logger.Info("Presence expired. " + presence.Configuration.Name); + var group = _groups.First(g => g.id == presence.Configuration.InterzoneGroupId); + SpawnRandomPresenseOfGroup(group); + } + + public override void Update(TimeSpan time) + { + foreach (var presence in _presences) + { + presence.Update(time); + } + } + + public override void Stop() + { + base.Stop(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs index 9eac49da2..d640fb977 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs @@ -12,7 +12,7 @@ public class Presence : INpcGroup { private ImmutableHashSet _flocks = ImmutableHashSet.Empty; - public PresenceConfiguration Configuration { get; private set; } + public IPresenceConfiguration Configuration { get; private set; } [NotNull] public IZone Zone { get; private set; } @@ -21,7 +21,7 @@ public class Presence : INpcGroup public IEnumerable Flocks => _flocks; - public Presence(IZone zone,PresenceConfiguration configuration) + public Presence(IZone zone, IPresenceConfiguration configuration) { Zone = zone; Configuration = configuration; @@ -101,15 +101,15 @@ 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) @@ -154,7 +154,7 @@ public Flock CreateFlock(IFlockConfiguration flockConfiguration) return FlockFactory(flockConfiguration, this); } - public virtual Area Area => Configuration.area; + public virtual Area Area => Configuration.Area; public virtual void LoadFlocks() { @@ -167,7 +167,7 @@ public override string ToString() return $"{Name}:{Configuration.ID}:{Zone.Id}"; } - public string Name => Configuration.name; + public string Name => Configuration.Name; public IEnumerable Members { diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs index 6b12ea28e..94930a12c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs @@ -2,37 +2,41 @@ namespace Perpetuum.Zones.NpcSystem.Presences { - public class PresenceConfiguration + public class PresenceConfiguration : IPresenceConfiguration { - public int ID { get; private set; } + public int ID { get; 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 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}"; } } @@ -42,10 +46,10 @@ public class DirectPresenceConfiguration : PresenceConfiguration 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..14020e9ad 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs @@ -7,24 +7,24 @@ 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"); @@ -35,18 +35,18 @@ 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"), + 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..ca031cd60 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs @@ -46,7 +46,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 +62,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..403742db2 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs @@ -8,6 +8,7 @@ public enum PresenceType Random = 3, DynamicPool = 4, FreeRoaming = 5, - Direct = 6 + Direct = 6, + Interzone = 7 } } \ 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 0e88ea8e5..c22f106ff 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs @@ -16,7 +16,7 @@ public class RoamingPresence : Presence public Position SpawnOrigin { get; private set; } public Point CurrentRoamingPosition { get; private set; } - public RoamingPresence(IZone zone,PresenceConfiguration configuration) : base(zone,configuration) + public RoamingPresence(IZone zone, IPresenceConfiguration configuration) : base(zone,configuration) { _fsm = new StackFSM(); _fsm.Push(new SpawnState(this)); @@ -52,7 +52,7 @@ public void Enter() _elapsed = TimeSpan.Zero; - _delay = TimeSpan.FromSeconds(_presence.Configuration.roamingRespawnSeconds * _repawnDelayModifier); + _delay = TimeSpan.FromSeconds(_presence.Configuration.RoamingRespawnSeconds * _repawnDelayModifier); _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs b/src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs new file mode 100644 index 000000000..4f1728922 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs @@ -0,0 +1,32 @@ +using Perpetuum.Log; +using Perpetuum.Threading.Process; +using System; + + +namespace Perpetuum.Zones.NpcSystem.Presences +{ + public class TestService : Process + { + + public TestService() + { + + } + public override void Update(TimeSpan time) + { + Console.WriteLine("=====================Update TestService=========================="); + Logger.Error("hey!!!!"); + } + + public override void Start() + { + Console.WriteLine("====================Start TestService============================"); + Logger.Error("this shit startin???!!!!"); + } + + public override void Stop() + { + Console.WriteLine("==================Stopping TestService=============================="); + } + } +} From 9177297e3ff54348969474818af3546a60426c4e Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 11 Mar 2020 14:32:32 -0400 Subject: [PATCH 140/457] figured out process not starting, iz npcs finally working --- .../PerpetuumBootstrapper.cs | 22 +------- src/Perpetuum/Perpetuum.csproj | 1 - src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 12 ++++- .../InterzonePresences/InterzonePresence.cs | 54 ++++++++++++++++++- .../InterzonePresenceConfigReader.cs | 2 +- .../InterzonePresenceManager.cs | 16 +++--- .../Zones/NpcSystem/Presences/TestService.cs | 32 ----------- 7 files changed, 73 insertions(+), 66 deletions(-) delete mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 942583581..985967e21 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1478,6 +1478,7 @@ public void RegisterNpcs() RegisterFlock(PresenceType.Random); RegisterFlock(PresenceType.Roaming); RegisterFlock(PresenceType.FreeRoaming); + RegisterFlock(PresenceType.Interzone); RegisterPresence(PresenceType.Normal); RegisterPresence(PresenceType.Direct).OnActivated(e => @@ -1655,11 +1656,6 @@ private void InitRelayManager() _builder.RegisterType(); - _builder.RegisterType().SingleInstance().OnActivated(e => - { - e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(5))); - }); - //TODO new EPBonusEventService _builder.RegisterType().SingleInstance().OnActivated(e => { @@ -1678,21 +1674,7 @@ private void InitRelayManager() }); //TODO new InterzoneNPCManager - //_builder.RegisterType().SingleInstance().OnActivated(e => - //{ - // Console.WriteLine("is this working?!"); - // try - // { - // e.Instance.Init(e.Context.Resolve(), e.Context.Resolve(), e.Context.Resolve()); - // var pm = e.Context.Resolve(); - // var timed = e.Instance.AsTimed(TimeSpan.FromSeconds(1)); - // pm.AddProcess(timed); - // } - // catch (Exception ex) - // { - // Console.WriteLine(ex.ToString()); - // } - //}); + RegisterAutoActivate(TimeSpan.FromSeconds(10)); _builder.RegisterType().As(); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 2c13f717b..0e8b564da 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -691,7 +691,6 @@ - diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index ec484a369..33158bc73 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -7,6 +7,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; namespace Perpetuum.Zones.NpcSystem.Flocks { @@ -164,7 +165,12 @@ protected virtual void OnNpcCreated(Npc npc) private NpcBehavior GetBehavior() { - if (Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresence) + + if( Presence is InterzonePresence) + { + return NpcBehavior.Create(Configuration.BehaviorType); + } + else if (Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresence) { // hogy ne tamadjanak be mindenkit rogton return NpcBehavior.Create(NpcBehaviorType.Neutral); @@ -199,6 +205,10 @@ private Position SpawnOriginSelector(Presence presence) { switch (presence) { + case InterzonePresence ip: + { + return Configuration.SpawnOrigin; + } case DynamicPresence dp: { return dp.DynamicPosition; diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index 7da5ea64d..a276d7819 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -1,4 +1,6 @@ -using System; +using Perpetuum.Zones.NpcSystem.Flocks; +using System; +using System.Text; namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { @@ -9,5 +11,55 @@ public InterzonePresence(IZone zone, IPresenceConfiguration configuration) : bas 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(); + } + + public override void LoadFlocks() + { + base.LoadFlocks(); + foreach(var flock in Flocks) + { + flock.SpawnAllMembers(); + } + } } } \ 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 index 3ea207f3f..b58523048 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs @@ -9,7 +9,7 @@ public class InterzonePresenceConfigReader : IInterzonePresenceConfigurationRead { public IEnumerable GetAll() { - var groups = Db.Query().CommandText("SELECT id, name FROM npcinterzonegroups").Execute().Select(CreateIZGroupFromRecord).ToArray(); + var groups = Db.Query().CommandText("SELECT id, name 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") .Execute() diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs index c8f92c029..05b2f77c3 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs @@ -17,24 +17,20 @@ public class InterzonePresenceManager : Process private IEnumerable _groups = new List(); private ImmutableList _presences = ImmutableList.Empty; - public IZoneManager _zoneManager; + public Lazy _zoneManager; - public InterzonePresenceManager() - { - - } - - public void Init(IZoneManager zoneManager, PresenceFactory presenceFactory, IInterzonePresenceConfigurationReader configurationReader) + public InterzonePresenceManager(Lazy zoneManager, PresenceFactory presenceFactory, IInterzonePresenceConfigurationReader configurationReader) { _presenceFactory = presenceFactory; _configurationReader = configurationReader; _zoneManager = zoneManager; } + public override void Start() { base.Start(); _groups = _configurationReader.GetAll(); - foreach(var group in _groups) + foreach (var group in _groups) { SpawnRandomPresenseOfGroup(group); } @@ -43,9 +39,9 @@ public override void Start() public void SpawnRandomPresenseOfGroup(InterzoneGroup group) { var config = group.GetRandom(); - var zone = _zoneManager.GetZone(config.ZoneID); + var zone = _zoneManager.Value.GetZone(config.ZoneID); var presence = _presenceFactory(zone, config); - + presence.LoadFlocks(); AddPresence(presence); } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs b/src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs deleted file mode 100644 index 4f1728922..000000000 --- a/src/Perpetuum/Zones/NpcSystem/Presences/TestService.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Perpetuum.Log; -using Perpetuum.Threading.Process; -using System; - - -namespace Perpetuum.Zones.NpcSystem.Presences -{ - public class TestService : Process - { - - public TestService() - { - - } - public override void Update(TimeSpan time) - { - Console.WriteLine("=====================Update TestService=========================="); - Logger.Error("hey!!!!"); - } - - public override void Start() - { - Console.WriteLine("====================Start TestService============================"); - Logger.Error("this shit startin???!!!!"); - } - - public override void Stop() - { - Console.WriteLine("==================Stopping TestService=============================="); - } - } -} From 9aa05362b99f58913c5d232b86989412d734425e Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 11 Mar 2020 14:46:15 -0400 Subject: [PATCH 141/457] formatting --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- .../Presences/InterzonePresences/InterzoneGroup.cs | 2 +- .../Presences/InterzonePresences/InterzonePresence.cs | 2 +- .../Zones/NpcSystem/Presences/PresenceConfiguration.cs | 6 +++--- .../NpcSystem/Presences/PresenceConfigurationReader.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 985967e21..685927a50 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -261,7 +261,7 @@ public IEnumerable GetCommands() public void Init(string gameRoot) { _builder = new ContainerBuilder(); - InitContainer(gameRoot); + InitContainer(gameRoot); _container = _builder.Build(); Logger.Current = _container.Resolve>(); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs index f8eb9ae72..a3764ceee 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs @@ -4,7 +4,7 @@ namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { public class InterzoneGroup { - public int id; + public int id; public string name; private List presences = new List(); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index a276d7819..1086d0711 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -56,7 +56,7 @@ private void OnFlockRemoved() public override void LoadFlocks() { base.LoadFlocks(); - foreach(var flock in Flocks) + foreach (var flock in Flocks) { flock.SpawnAllMembers(); } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs index 94930a12c..d65496ec2 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs @@ -4,7 +4,7 @@ namespace Perpetuum.Zones.NpcSystem.Presences { public class PresenceConfiguration : IPresenceConfiguration { - public int ID { get; set; } + public int ID { get; private set; } public string Name { get; set; } public Area Area { get; set; } @@ -26,7 +26,7 @@ public class PresenceConfiguration : IPresenceConfiguration public int ZoneID { get; set; } - public Position RandomCenter => new Position((double) RandomCenterX,(double) RandomCenterY); + public Position RandomCenter => new Position((double)RandomCenterX, (double)RandomCenterY); public PresenceConfiguration(int id, PresenceType presenceType) { @@ -44,7 +44,7 @@ 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; diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs index 14020e9ad..94fab3175 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs @@ -27,7 +27,7 @@ public IEnumerable GetAll(int zoneID) 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"); From 8c1646ce2fb286238cd3210db550c12df866eba6 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Mon, 16 Mar 2020 21:12:43 -0400 Subject: [PATCH 142/457] add spawn timer between spawns, refactor responsibility for presence management to group level --- .../PerpetuumBootstrapper.cs | 2 +- src/Perpetuum/Perpetuum.csproj | 1 - .../IInterzonePresenceConfigurationReader.cs | 2 +- .../IInterzonePresenceManager.cs | 8 -- .../InterzonePresences/InterzoneGroup.cs | 119 ++++++++++++++++-- .../InterzonePresenceConfigReader.cs | 18 +-- .../InterzonePresenceManager.cs | 50 ++------ 7 files changed, 128 insertions(+), 72 deletions(-) delete mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 685927a50..42d56d64c 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1440,7 +1440,7 @@ public void RegisterNpcs() _builder.RegisterType().As(); _builder.RegisterType().As(); _builder.RegisterType().As(); - _builder.RegisterType(); + _builder.RegisterType().As(); _builder.RegisterType().OnActivated(e => { var pm = e.Context.Resolve(); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 0e8b564da..9be9b7a7f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -676,7 +676,6 @@ - diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs index f735ebde5..f158ced1e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceConfigurationReader.cs @@ -4,6 +4,6 @@ namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { public interface IInterzonePresenceConfigurationReader { - IEnumerable GetAll(); + IEnumerable GetAll(); } } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs deleted file mode 100644 index 3199e39b2..000000000 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/IInterzonePresenceManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Perpetuum.Threading.Process; - -namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences -{ - public interface IInterzonePresenceManager : IProcess - { - } -} \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs index a3764ceee..03aa6b629 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs @@ -1,26 +1,125 @@ -using System.Collections.Generic; +using Perpetuum.Log; +using Perpetuum.Timers; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { - public class InterzoneGroup + public interface IInterzoneGroup { - public int id; - public string name; - private List presences = new List(); + 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) { - this.presences.AddMany(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(); + 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()); } - public List GetAll() + private void OnPresenceExpired(Presence presence) { - return this.presences; + 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; } - public IPresenceConfiguration GetRandom() + private bool IsSpawned() { - return presences.RandomElement(); + 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/InterzonePresenceConfigReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs index b58523048..835a6e59d 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs @@ -7,9 +7,9 @@ namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { public class InterzonePresenceConfigReader : IInterzonePresenceConfigurationReader { - public IEnumerable GetAll() + public IEnumerable GetAll() { - var groups = Db.Query().CommandText("SELECT id, name FROM npcinterzonegroup").Execute().Select(CreateIZGroupFromRecord).ToArray(); + 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") .Execute() @@ -17,19 +17,19 @@ public IEnumerable GetAll() foreach (var group in groups) { - var configs = presenceConfigs.Where(p => group.id == p.InterzoneGroupId); + var configs = presenceConfigs.Where(p => group.GetId() == p.InterzoneGroupId); group.AddConfigs(configs); } return groups; } - private static InterzoneGroup CreateIZGroupFromRecord(IDataRecord record) + private static IInterzoneGroup CreateIZGroupFromRecord(IDataRecord record) { - var g = new InterzoneGroup() - { - id = record.GetValue("id"), - name = record.GetValue("name") - }; + 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; } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs index 05b2f77c3..cc30a6e55 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceManager.cs @@ -1,23 +1,15 @@ -using Perpetuum.Log; -using Perpetuum.Threading.Process; +using Perpetuum.Threading.Process; using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { public class InterzonePresenceManager : Process { - - public delegate IInterzonePresenceManager Factory(IZoneManager zoneManager, PresenceFactory presenceFactory); - - private PresenceFactory _presenceFactory; - private IInterzonePresenceConfigurationReader _configurationReader; - private IEnumerable _groups = new List(); - private ImmutableList _presences = ImmutableList.Empty; - public Lazy _zoneManager; + private readonly PresenceFactory _presenceFactory; + private readonly IInterzonePresenceConfigurationReader _configurationReader; + private IEnumerable _groups = new List(); public InterzonePresenceManager(Lazy zoneManager, PresenceFactory presenceFactory, IInterzonePresenceConfigurationReader configurationReader) { @@ -28,45 +20,19 @@ public InterzonePresenceManager(Lazy zoneManager, PresenceFactory public override void Start() { - base.Start(); _groups = _configurationReader.GetAll(); foreach (var group in _groups) { - SpawnRandomPresenseOfGroup(group); + group.Init(_zoneManager.Value, _presenceFactory); } - } - - public void SpawnRandomPresenseOfGroup(InterzoneGroup group) - { - var config = group.GetRandom(); - var zone = _zoneManager.Value.GetZone(config.ZoneID); - var presence = _presenceFactory(zone, config); - presence.LoadFlocks(); - AddPresence(presence); - } - - public void AddPresence(Presence presence) - { - Logger.Info("Adding Presence: " + presence.Configuration.Name); - ImmutableInterlocked.Update(ref _presences, p => p.Add(presence)); - - if (presence is INotifyPresenceExpired notifier) - notifier.PresenceExpired += OnPresenceExpired; - } - - private void OnPresenceExpired(Presence presence) - { - ImmutableInterlocked.Update(ref _presences, p => p.Remove(presence)); - Logger.Info("Presence expired. " + presence.Configuration.Name); - var group = _groups.First(g => g.id == presence.Configuration.InterzoneGroupId); - SpawnRandomPresenseOfGroup(group); + base.Start(); } public override void Update(TimeSpan time) { - foreach (var presence in _presences) + foreach (var group in _groups) { - presence.Update(time); + group.Update(time); } } From 41c80ae962ea484b201061dc7b18009dcacf8023 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 19 Mar 2020 20:06:01 -0400 Subject: [PATCH 143/457] double rates for harvest and solid ores vs liquid ores --- src/Perpetuum/Modules/DrillerModule.cs | 33 ++++++++++++++-- src/Perpetuum/Modules/HarvesterModule.cs | 50 ++++++++++++------------ 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index 5396cf2ff..fa74c99a4 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Linq; using System.Transactions; using Perpetuum.Data; using Perpetuum.EntityFramework; @@ -29,7 +31,6 @@ public DrillerModule(CategoryFlags ammoCategoryFlags,RareMaterialHandler rareMat _materialHelper = materialHelper; _miningAmountModifier = new MiningAmountModifierProperty(this); AddProperty(_miningAmountModifier); - } public override void AcceptVisitor(IEntityVisitor visitor) @@ -111,7 +112,6 @@ private void DoExtractMinerals(IZone zone) var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); var materialAmount = materialInfo.Amount * _miningAmountModifier.Value; - var extractedMaterials = Extract(mineralLayer, terrainLock.Location,(uint) materialAmount); extractedMaterials.Count.ThrowIfEqual(0, ErrorCodes.NoMineralOnTile); @@ -147,7 +147,7 @@ private void DoExtractMinerals(IZone zone) //save container container.Save(); - OnGathererMaterial(zone, player); + OnGathererMaterial(zone, player, (int) materialInfo.Type); Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); @@ -165,5 +165,32 @@ private void CheckEnablerEffect(MaterialInfo materialInfo) var containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); } + + private const int MAX_EP_PER_DAY = 1440; + + private readonly ISet LIQUIDS = new HashSet(new int[]{ (int)MaterialType.Crude, (int)MaterialType.Liquizit, (int)MaterialType.Epriton }); + + protected override int CalculateEp(int materialType) + { + 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; + + if (LIQUIDS.Contains(materialType)) + chance /= 2.0; + + var rand = FastRandom.NextDouble(); + if (rand <= chance) + return 1; + + return 0; + } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index 29b378716..0f064ae67 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -25,33 +25,13 @@ protected GathererModule(CategoryFlags ammoCategoryFlags, bool ranged = false) : cycleTime.AddEffectModifier(AggregateField.effect_gathering_cycle_time_modifier); } - private const int MAX_EP_PER_DAY = 720; + protected abstract int CalculateEp(int materialType); - 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) + protected void OnGathererMaterial(IZone zone, Player player, int materialType) { if (zone.Configuration.Type == ZoneType.Training) return; - var ep = CalculateEp(); + var ep = CalculateEp(materialType); if (zone.Configuration.IsBeta) ep *= 2; @@ -95,6 +75,28 @@ public override void UpdateProperty(AggregateField field) } } + private const int MAX_EP_PER_DAY = 1440; + + protected override int CalculateEp(int materialType) + { + 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 override void OnAction() { var zone = Zone; @@ -141,7 +143,7 @@ private void DoHarvesting(IZone zone) //everything went ok, save container container.Save(); - OnGathererMaterial(zone, player); + OnGathererMaterial(zone, player, (int) plantInfo.type); Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); From e965c81d43f2990301f2726b220a4d3bc596418e Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 20 Mar 2020 00:17:25 -0400 Subject: [PATCH 144/457] Artifactrefresher process, tricky ignore entry --- .gitignore | 1 - .../PerpetuumBootstrapper.cs | 3 ++- src/Perpetuum/Perpetuum.csproj | 1 + .../Services/Artifacts/ArtifactRefresher.cs | 20 +++++++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs diff --git a/.gitignore b/.gitignore index 3c4efe206..0cd65a833 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ dlldata.c # DNX project.lock.json project.fragment.lock.json -artifacts/ *_i.c *_p.c diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index dc45b8991..d2982637f 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -76,6 +76,7 @@ using Perpetuum.RequestHandlers.Zone.PBS; using Perpetuum.RequestHandlers.Zone.StatsMapDrawing; using Perpetuum.Robots; +using Perpetuum.Services; using Perpetuum.Services.Channels; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventProcessors; @@ -445,6 +446,7 @@ private void RegisterAutoActivatedTypes() RegisterAutoActivate(TimeSpan.FromMinutes(5)); RegisterAutoActivate(TimeSpan.FromMinutes(10)); RegisterAutoActivate(TimeSpan.FromMinutes(10)); + RegisterAutoActivate(TimeSpan.FromHours(7)); } private void RegisterCommands() @@ -1668,7 +1670,6 @@ private void InitRelayManager() e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); }); - _builder.RegisterType().As(); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d58b81a7a..d681203f5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -284,6 +284,7 @@ + diff --git a/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs b/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs new file mode 100644 index 000000000..1ab7d2617 --- /dev/null +++ b/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs @@ -0,0 +1,20 @@ +using Perpetuum.Data; +using Perpetuum.Threading.Process; +using System; + +namespace Perpetuum.Services +{ + public class ArtifactRefresher : Process + { + public override void Update(TimeSpan time) + { + DoRefresh(); + } + + public void DoRefresh() + { + Console.WriteLine("Refreshing Artifacts"); + Db.Query().CommandText("EXEC [opp].[artifactRefresh];").ExecuteNonQuery(); + } + } +} From e12d8fb4288f6962a151d6ceb654f22fc81b961a Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 21 Mar 2020 19:11:58 -0400 Subject: [PATCH 145/457] new flagging behaviour for chassis scanner --- src/Perpetuum/Modules/ActiveModule.cs | 16 ++++++++++------ src/Perpetuum/Players/Player.cs | 2 +- .../Zones/Scanning/Modules/ItemScannerModule.cs | 8 ++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 7b3bc0412..b71928ebd 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -305,6 +305,15 @@ public void Update(TimeSpan time) protected abstract void OnAction(); + protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) + { + if (parentPlayer != null) + { + // pvp ellenorzes + (unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError(); + } + } + protected Lock GetLock() { var currentLock = Lock.ThrowIfNull(ErrorCodes.LockTargetNotFound); @@ -319,12 +328,7 @@ protected Lock GetLock() if (ED.AttributeFlags.OffensiveModule) { - if ((parentPlayer != null)) - { - // pvp ellenorzes - (unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError(); - } - + HandleOffensivePVPCheck(parentPlayer, unitLockTarget); Debug.Assert(ParentRobot != null, "ParentRobot != null"); ParentRobot.OnAggression(unitLockTarget.Target); } diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index e2503fd6e..8406f48eb 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -743,7 +743,7 @@ public override void OnAggression(Unit victim) var zone = Zone; if (zone == null) return; - + return; if (zone.Configuration.Protected) { // PROTECTED ISLANDS diff --git a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs index 90dabd36a..d9a8bca6a 100644 --- a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs +++ b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs @@ -16,6 +16,14 @@ protected ItemScannerModule() : base(true) } + protected override void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) + { + if (parentPlayer != null) + { + parentPlayer.CheckPvp().ThrowIfError(); + } + } + protected override void OnAction() { var unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); From 8b1fef36cdef06eec9cd838701d915aa9e6f0142 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 21 Mar 2020 20:37:31 -0400 Subject: [PATCH 146/457] remove application of Aggressor flag --- src/Perpetuum/Players/Player.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 8406f48eb..6d47c4353 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -739,23 +739,6 @@ public override void OnAggression(Unit victim) if (HasPvpEffect && victimPlayer.HasPvpEffect) return; - - var zone = Zone; - if (zone == null) - return; - return; - if (zone.Configuration.Protected) - { - // PROTECTED ISLANDS - if (!victimPlayer.HasPvpEffect && !victimPlayer.HasAggressorEffect) - ApplyAggressorEffect(); - } - else - { - //PVP ISLANDS - if (victimPlayer.States.BigBrother && !victimPlayer.HasPvpEffect) - ApplyAggressorEffect(); - } } public void OnPvpSupport(Unit target) From e279af89c52cb85c30b0340b841ee7b1499d575f Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 21 Mar 2020 20:38:08 -0400 Subject: [PATCH 147/457] handle other cases when scanner should be allowed and attacker will become flagged --- src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs index d9a8bca6a..a4335e2ab 100644 --- a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs +++ b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs @@ -18,6 +18,10 @@ protected ItemScannerModule() : base(true) protected override void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) { + var targetPlayer = unitLockTarget.Target as Player; + if (targetPlayer != null && targetPlayer.HasPvpEffect || !targetPlayer.Zone.Configuration.Protected) { + return; + } if (parentPlayer != null) { parentPlayer.CheckPvp().ThrowIfError(); From 767a9975a91f76387d60e997695f9d3e13c93f66 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 3 Apr 2020 00:35:34 -0400 Subject: [PATCH 148/457] Support for Mobile stronghold teleport definition --- .../PerpetuumBootstrapper.cs | 3 + src/Perpetuum/Perpetuum.csproj | 2 + .../Teleporting/MobileStrongholdTeleport.cs | 62 +++++++++++++++++++ .../StrongholdTeleportTargetHelper.cs | 42 +++++++++++++ .../Zones/Teleporting/TeleportVisitor.cs | 1 + 5 files changed, 110 insertions(+) create mode 100644 src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs create mode 100644 src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index dc45b8991..78b1a0534 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -963,6 +963,7 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit(); RegisterUnit(); + RegisterUnit(); // New Field device RegisterUnit(); RegisterUnit(); RegisterPBSObject(); @@ -1213,6 +1214,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_simple_switch); ByCategoryFlags(CategoryFlags.cf_item_supply); ByCategoryFlags(CategoryFlags.cf_mobile_world_teleport); + ByNamePatternAndFlag("def_mobile_teleport_strnghld", CategoryFlags.cf_mobile_world_teleport); // TODO new field dev ByCategoryFlags(CategoryFlags.cf_mission_coin); ByCategoryFlags(CategoryFlags.cf_area_bomb); ByCategoryFlags(CategoryFlags.cf_plasma_bomb); @@ -2540,6 +2542,7 @@ void RegisterZone(ZoneType type) where T:Zone _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d58b81a7a..ed179bc89 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -878,6 +878,8 @@ + + diff --git a/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs new file mode 100644 index 000000000..a542d65e3 --- /dev/null +++ b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs @@ -0,0 +1,62 @@ +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); + } + + 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).ToList(); + + (testlist.Count == 0).ThrowIfTrue(ErrorCodes.NoTeleportTargetsWereFound); + + base.CheckDeploymentAndThrow(zone, spawnPosition); + } + } +} diff --git a/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs b/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs new file mode 100644 index 000000000..33f1b7a34 --- /dev/null +++ b/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.Teleporting +{ + public class StrongholdTeleportTargetHelper + { + private readonly IZoneManager _zoneManager; + private readonly TeleportDescriptionBuilder.Factory _descriptionBuilderFactory; + + public StrongholdTeleportTargetHelper(IZoneManager zoneManager, TeleportDescriptionBuilder.Factory descriptionBuilderFactory) + { + _zoneManager = zoneManager; + _descriptionBuilderFactory = descriptionBuilderFactory; + } + + public IEnumerable GetStrongholdTargets(IZone zone, long sourceTeleportEid, int teleportRange) + { + var result = new List(); + var descriptionId = 0; + foreach (var teleportColumn in _zoneManager.Zones.OfType().GetUnits().Where(tc => tc.IsEnabled && tc.Zone != zone)) + { + + 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 From e63e207c6f4527a208d4820b8c3673c7b7e89585 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 3 Apr 2020 00:36:42 -0400 Subject: [PATCH 149/457] not worth the 2 chars --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 78b1a0534..438fc4234 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1214,7 +1214,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_simple_switch); ByCategoryFlags(CategoryFlags.cf_item_supply); ByCategoryFlags(CategoryFlags.cf_mobile_world_teleport); - ByNamePatternAndFlag("def_mobile_teleport_strnghld", CategoryFlags.cf_mobile_world_teleport); // TODO new field dev + ByNamePatternAndFlag("def_mobile_teleport_stronghold", CategoryFlags.cf_mobile_world_teleport); // TODO new field dev ByCategoryFlags(CategoryFlags.cf_mission_coin); ByCategoryFlags(CategoryFlags.cf_area_bomb); ByCategoryFlags(CategoryFlags.cf_plasma_bomb); From 4ff5ad6a9c6265ccea900bd4cfa3fac52c05c7a8 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 7 Apr 2020 13:48:33 -0400 Subject: [PATCH 150/457] remove random rift entrance behaviour --- src/Perpetuum/Services/RiftSystem/Rift.cs | 15 +------- .../Services/RiftSystem/RiftManager.cs | 36 +------------------ 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index 0c9a2d8a0..a0d8d3961 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -205,22 +205,9 @@ public void UseItem(Player player) teleport.DoTeleportAsync(player); return; } - - // teleport player to stronghold - // stronghold zone must be active. - if (this.DestinationStrongholdZone > 0) - { - var destZone = player.Character.GetZone(DestinationStrongholdZone); - var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); - // there should only be one, for now. - // FIXME: some zones may have more than one stronghold. - // we will need to link anomalies like teleporters. - var pos = destZone.Units.OfType().First(); - teleport.TargetPosition = pos.CurrentPosition; - teleport.DoTeleportAsync(player); - } else { + // Regular jump behaviour player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index c6fcc1bfb..02502aab6 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -62,20 +62,15 @@ public class RiftManager private readonly TimeRange _spawnTime; private readonly RiftSpawnPositionFinder _spawnPositionFinder; private readonly IEntityServices _entityServices; - private readonly ZoneManager _zonemanager; - private bool StrongHoldRiftGenerated { get; set; } = false; - private Random r; private readonly LinkedList _nextRiftSpawns = new LinkedList(); - public RiftManager(IZone zone, TimeRange spawnTime, RiftSpawnPositionFinder spawnPositionFinder, IEntityServices entityServices, ZoneManager zoneManager) + public RiftManager(IZone zone, TimeRange spawnTime, RiftSpawnPositionFinder spawnPositionFinder, IEntityServices entityServices) { _zone = zone; _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; - _zonemanager = zoneManager; - r = new Random(); } private int _riftCounts; @@ -106,23 +101,6 @@ public void Update(TimeSpan time) }); } - /// - /// Get a random active stronghold zone. - /// - /// int - private int GetRandomStrongHoldZone() - { - IEnumerable strongholdzones = _zonemanager.Zones.OfType(); - // less than elegant. if we only have one stronghold zone just return it. - if (strongholdzones.Count() == 1) - { - return strongholdzones.ElementAt(0).Id; - } - int index = r.Next(0, strongholdzones.Count()); - Zone selzone = strongholdzones.ElementAt(index); - return selzone.Id; - } - private void SpawnRift() { var rift = (Rift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.RIFT); @@ -130,18 +108,6 @@ private void SpawnRift() rift.SetDespawnTime(TimeSpan.FromHours(3)); rift.RemovedFromZone += OnRiftRemovedFromZone; - // generate a stronghold teleport and make it random chance. - // make sure we have at least one stronghold enabled. - if (_zonemanager.Zones.OfType().Count() > 0) - { - int rand = r.Next(0, 10); - if (rand < 2 && !StrongHoldRiftGenerated) - { - rift.DestinationStrongholdZone = GetRandomStrongHoldZone(); - rift.OriginZone = this._zone.Id; - } - } - var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); if (_zone is StrongHoldZone) From 19eac36c407e1dfc054eac8455ecf141fd32df51 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 7 Apr 2020 14:50:51 -0400 Subject: [PATCH 151/457] rm unused rift properties, rip. add check for tap use on stronghold exit --- src/Perpetuum/Services/Channels/GameAdminCommands.cs | 2 +- src/Perpetuum/Services/RiftSystem/Rift.cs | 12 ++++++++---- src/Perpetuum/Services/RiftSystem/RiftManager.cs | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 6e62df231..5ba6817b0 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -752,7 +752,7 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C var rift = z.Units.OfType(); foreach (Rift r in rift) { - channel.SendMessageToAll(sessionManager, sender, string.Format("Rift - Zone: {0}, Position: ({1}), Destination Zone:{2}", r.Zone, r.CurrentPosition, r.DestinationStrongholdZone)); + channel.SendMessageToAll(sessionManager, sender, string.Format("Rift - Zone: {0}, Position: ({1})", r.Zone, r.CurrentPosition)); } } } diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index a0d8d3961..e47d55fb7 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -114,6 +114,10 @@ public override void Deploy(IZone zone, Player player) if (ED.Tier.level > maxRiftLevel) throw new PerpetuumException(ErrorCodes.RiftLevelMismatch); + if (zone is StrongHoldZone) + throw new PerpetuumException(ErrorCodes.ItemNotUsable); + + var info = new RiftNpcGroupInfo(); Debug.Assert(DeployableItemEntityDefault.Config.npcPresenceId != null, "DeployableItemEntityDefault.Config.npcPresenceId != null"); info.presenceID = (int)DeployableItemEntityDefault.Config.npcPresenceId; @@ -145,9 +149,6 @@ public Rift(ITeleportStrategyFactories teleportStrategyFactories) _blobEmitter = new BlobEmitter(this); } - public int DestinationStrongholdZone { get; set; } - public int OriginZone { get; set; } - public void SetDespawnTime(TimeSpan despawnTime) { _despawnHelper = UnitDespawnHelper.Create(this,despawnTime); @@ -166,7 +167,10 @@ public bool TryActivate(RiftNpcGroupInfo info) var zone = Zone; if (zone == null) return false; - + + if (zone is StrongHoldZone) + return false; + if (Interlocked.CompareExchange(ref _activated, 1, 0) == 1) return false; diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index 02502aab6..2d42a9e6c 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -117,7 +117,7 @@ private void SpawnRift() } rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); - Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2}) Stronghold Zone ID: {3}", _zone.Id, rift.ED.Name, rift.CurrentPosition, rift.DestinationStrongholdZone)); + Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2})", _zone.Id, rift.ED.Name, rift.CurrentPosition)); } private void OnRiftRemovedFromZone(Unit unit) From 1f01f73b2c7f202af3994ccc6ebd2199f0f86b81 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 9 Apr 2020 15:53:06 -0400 Subject: [PATCH 152/457] make iz presence roaming too, presence refactors --- .../PerpetuumBootstrapper.cs | 9 +- src/Perpetuum/Perpetuum.csproj | 1 + .../InterzonePresences/InterzonePresence.cs | 21 +- .../PathFinders/FreeRoamingPathFinder.cs | 11 +- .../PathFinders/IRoamingPathFinder.cs | 4 +- .../PathFinders/NormalRoamingPathFinder.cs | 4 +- .../Presences/PathFinders/RoamingStates.cs | 148 ++++++++++++++ .../Zones/NpcSystem/Presences/Presence.cs | 6 +- .../NpcSystem/Presences/RoamingPresence.cs | 184 +++--------------- 9 files changed, 210 insertions(+), 178 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 42d56d64c..290ae980c 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1478,7 +1478,7 @@ public void RegisterNpcs() RegisterFlock(PresenceType.Random); RegisterFlock(PresenceType.Roaming); RegisterFlock(PresenceType.FreeRoaming); - RegisterFlock(PresenceType.Interzone); + RegisterFlock(PresenceType.Interzone); RegisterPresence(PresenceType.Normal); RegisterPresence(PresenceType.Direct).OnActivated(e => @@ -1502,7 +1502,7 @@ public void RegisterNpcs() var 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) { @@ -1516,6 +1516,11 @@ public void RegisterNpcs() roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); break; } + case PresenceType.Interzone: + { + roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); + break; + } } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 9be9b7a7f..d0f6124e5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -685,6 +685,7 @@ + diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index 1086d0711..0d6cea92f 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -1,17 +1,32 @@ -using Perpetuum.Zones.NpcSystem.Flocks; -using System; +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 InterzonePresence : DynamicPresence + public class InterzonePresence : DynamicPresence, IRoamingPresence { + public StackFSM StackFSM { get; } + public Position SpawnOrigin { get; set; } + public Point CurrentRoamingPosition { get; set; } + public IRoamingPathFinder PathFinder { get; set; } public InterzonePresence(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 OnFlockAdded(Flock flock) { diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs index e99713e73..c05fa6731 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.Linq; using Perpetuum.Collections; +using Perpetuum.ExportedTypes; using Perpetuum.PathFinders; using Perpetuum.Zones.NpcSystem.Flocks; @@ -21,7 +22,7 @@ public FreeRoamingPathFinder(IZone zone) _direction = FastRandom.NextDouble(); } - public Point FindSpawnPosition(RoamingPresence presence) + public Point FindSpawnPosition(IRoamingPresence presence) { var homeRange = presence.Flocks.Max(f => f.HomeRange); var rangeMax = homeRange * 2; @@ -30,7 +31,7 @@ public Point FindSpawnPosition(RoamingPresence presence) 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); @@ -50,7 +51,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 +60,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 +77,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/RoamingStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs new file mode 100644 index 000000000..c3397360b --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -0,0 +1,148 @@ +using Perpetuum.StateMachines; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem.Flocks; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.Presences.PathFinders +{ + + public class SpawnState : IState + { + private readonly IRoamingPresence _presence; + private TimeSpan _delay = TimeSpan.Zero; + + private bool _spawning; + private bool _spawned; + private double _repawnDelayModifier = 0.0; + + public SpawnState(IRoamingPresence 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.StackFSM.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(); + } + } + } + + public class RoamingState : IState + { + private readonly IRoamingPresence _presence; + + public RoamingState(IRoamingPresence 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.StackFSM.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() + { +#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/Presence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs index d640fb977..deeb4803f 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs @@ -81,7 +81,7 @@ public void Update(TimeSpan time) { _updateTimer.Update(time); - if ( !_updateTimer.Passed ) + if (!_updateTimer.Passed) return; OnUpdate(_updateTimer.Elapsed); @@ -176,10 +176,10 @@ public IEnumerable Members public void AddDebugInfoToDictionary(IDictionary dictionary) { - + } - protected void Log(string message) + public virtual void Log(string message) { Logger.Info($"[Presence] ({ToString()}) - {message}"); } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs index c22f106ff..7f6499a2c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs @@ -1,180 +1,42 @@ using System; -using System.Diagnostics; +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, IPresenceConfiguration 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); + } + 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) - { - _fsm.Update(time); - } - - #region Roaming States - - private class SpawnState : IState + public RoamingPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { - 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(); - } - } + StackFSM = new StackFSM(); + StackFSM.Push(new SpawnState(this)); } - 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() - { -#if DEBUG - if (!Debugger.IsAttached) - { - _presence.Log("finding new roaming position. current: " + _presence.CurrentRoamingPosition); - } -#endif - var nextRoamingPosition = _presence.PathFinder.FindNextRoamingPosition(_presence); -#if DEBUG - if (!Debugger.IsAttached) - { - _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(); - } - } + StackFSM.Update(time); } - - #endregion } } \ No newline at end of file From cbf5c63b0c173f42c60e6e12753878c92320f931 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 9 Apr 2020 16:31:23 -0400 Subject: [PATCH 153/457] support both roaming and static presences with izpresence --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 4 +++- .../InterzonePresences/InterzonePresence.cs | 13 +++++++++++-- .../Zones/NpcSystem/Presences/PresenceType.cs | 3 ++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 290ae980c..75a3d83ec 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1479,6 +1479,7 @@ public void RegisterNpcs() RegisterFlock(PresenceType.Roaming); RegisterFlock(PresenceType.FreeRoaming); RegisterFlock(PresenceType.Interzone); + RegisterFlock(PresenceType.InterzoneRoaming); RegisterPresence(PresenceType.Normal); RegisterPresence(PresenceType.Direct).OnActivated(e => @@ -1491,6 +1492,7 @@ public void RegisterNpcs() RegisterPresence(PresenceType.Roaming); RegisterPresence(PresenceType.FreeRoaming); RegisterPresence(PresenceType.Interzone); + RegisterPresence(PresenceType.InterzoneRoaming); _builder.Register(x => { @@ -1516,7 +1518,7 @@ public void RegisterNpcs() roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); break; } - case PresenceType.Interzone: + case PresenceType.InterzoneRoaming: { roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); break; diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index 0d6cea92f..8947394d9 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -7,13 +7,13 @@ namespace Perpetuum.Zones.NpcSystem.Presences.InterzonePresences { - public class InterzonePresence : DynamicPresence, IRoamingPresence + 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 InterzonePresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) + public InterzoneRoamingPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { if (Configuration.DynamicLifeTime != null) LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); @@ -27,6 +27,15 @@ protected override void OnUpdate(TimeSpan time) StackFSM.Update(time); base.OnUpdate(time); } + } + + 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) { diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs index 403742db2..9bfa6b5e7 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs @@ -9,6 +9,7 @@ public enum PresenceType DynamicPool = 4, FreeRoaming = 5, Direct = 6, - Interzone = 7 + Interzone = 7, + InterzoneRoaming = 8 } } \ No newline at end of file From ea9540300860661dfc4c91d30de29ad1fe50c391 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 9 Apr 2020 22:21:20 -0400 Subject: [PATCH 154/457] use logger --- src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs b/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs index 1ab7d2617..746c78b2b 100644 --- a/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs +++ b/src/Perpetuum/Services/Artifacts/ArtifactRefresher.cs @@ -1,4 +1,5 @@ using Perpetuum.Data; +using Perpetuum.Log; using Perpetuum.Threading.Process; using System; @@ -13,7 +14,7 @@ public override void Update(TimeSpan time) public void DoRefresh() { - Console.WriteLine("Refreshing Artifacts"); + Logger.Info("Artifact Refresh"); Db.Query().CommandText("EXEC [opp].[artifactRefresh];").ExecuteNonQuery(); } } From edc165adbd755c7f18240850646f3f579dbd97ab Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 10 Apr 2020 15:33:26 -0400 Subject: [PATCH 155/457] handle player if target is player --- .../Zones/Scanning/Modules/ItemScannerModule.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs index a4335e2ab..b2a22ae62 100644 --- a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs +++ b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs @@ -18,13 +18,16 @@ protected ItemScannerModule() : base(true) protected override void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) { - var targetPlayer = unitLockTarget.Target as Player; - if (targetPlayer != null && targetPlayer.HasPvpEffect || !targetPlayer.Zone.Configuration.Protected) { - return; - } - if (parentPlayer != null) + if (unitLockTarget.Target is Player targetPlayer) { - parentPlayer.CheckPvp().ThrowIfError(); + if (targetPlayer != null && targetPlayer.HasPvpEffect || !targetPlayer.Zone.Configuration.Protected) + { + return; + } + if (parentPlayer != null) + { + parentPlayer.CheckPvp().ThrowIfError(); + } } } From 20267e32cb0404a089fa70d8cc0d63fc69f79859 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 10 Apr 2020 16:12:02 -0400 Subject: [PATCH 156/457] missing implementation on the request handler --- src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs b/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs index f225074c7..005c359a5 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; } + } }); @@ -158,7 +165,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); } } From 5ae2a559295b5d7c9152082164e5486ba79463b1 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 10 Apr 2020 16:34:04 -0400 Subject: [PATCH 157/457] typo and year update --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 9fb04ade2..19fc4b70d 100644 --- a/LICENSE +++ b/LICENSE @@ -30,7 +30,7 @@ OpenPerpetuum - LICENSE MIT License -Copyright (c) 2018-2019 OpenPerpetuum +Copyright (c) 2018-2020 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 diff --git a/README.md b/README.md index cb1793861..67440e5c4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # The Open Perpetuum Server This is a fork of the Official Perpetuum Server as released by Avatar Creations [here](https://github.com/PerpetuumOnline/PerpetuumServer). -This server is developed under the direction of the [Open Perpetuum Project](https://openperpetuum.com) game design and development team; a 100% volunteer opensource player development and persistant Perpetuum Server hosting project. +This server is developed under the direction of the [Open Perpetuum Project](https://openperpetuum.com) game design and development team; a 100% volunteer opensource player development and persistent Perpetuum Server hosting project. To join the Team, find our call for volunteers on our website [here!](https://openperpetuum.com/volunteer-tech) where you will also find our [Volunteer Survey](https://forms.gle/V7B5zNAFCFmSLLxt6). From 95bf8416a5e3a9ab78aab8e404de773c44f17655 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 18 Apr 2020 17:10:13 -0400 Subject: [PATCH 158/457] restrict stronghold tele to alpha for now --- src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs index a542d65e3..f6b6cba31 100644 --- a/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs +++ b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs @@ -56,6 +56,10 @@ public override void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) (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); } } From 9d52c583dcf9cfacab43f07e79ae3dc3d659c54d Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 21 Apr 2020 17:02:34 -0400 Subject: [PATCH 159/457] hitsize mod for los calc --- src/Perpetuum/Zones/LineOfSight.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/LineOfSight.cs b/src/Perpetuum/Zones/LineOfSight.cs index b5c0467e2..0bff2ae7e 100644 --- a/src/Perpetuum/Zones/LineOfSight.cs +++ b/src/Perpetuum/Zones/LineOfSight.cs @@ -55,7 +55,8 @@ 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.6; + len = (float)CylinderIntersection(source, direction, targetUnit.CurrentPosition.ToVector3(), target, hitsize); } return IsInLineOfSight(zone,source,direction,len,ballistic); From 5e99b3a5e64175b6d2fa2181b4de0d46db079a9c Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 25 Apr 2020 19:28:35 -0400 Subject: [PATCH 160/457] migrate scaling of prod costs to db, store as lazy cache --- src/Perpetuum/Keywords.cs | 2 + src/Perpetuum/Perpetuum.csproj | 1 + .../ProductionEngine/IProductionDataAccess.cs | 2 + .../ProductionEngine/ProductionCost.cs | 11 +++ .../ProductionEngine/ProductionDataAccess.cs | 72 ++++++++++++++++--- .../ProductionDataAccessExtensions.cs | 35 ++------- 6 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 src/Perpetuum/Services/ProductionEngine/ProductionCost.cs diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index cbc502dd2..c63203e5b 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1072,6 +1072,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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 3f4f3940e..22e81b609 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -301,6 +301,7 @@ + diff --git a/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs b/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs index 7a9ccb06c..1d4209ce6 100644 --- a/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs +++ b/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs @@ -11,6 +11,8 @@ public interface IProductionDataAccess IDictionary ResearchLevels { get; } ILookup ProductionComponents { get; } IDictionary ProductionDurations { get; } + IDictionary ProductionCostByCategory { get; } + IDictionary ProductionCostByTechLevel { get; } IDictionary CalibrationDefaults { get; } ProductionDecalibration GetDecalibration(int targetDefinition); diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs b/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs new file mode 100644 index 000000000..9e1fa664c --- /dev/null +++ b/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs @@ -0,0 +1,11 @@ + +namespace Perpetuum.Services.ProductionEngine +{ + public class ProductionCost + { + public long? categoryFlag; + public int? tierType; + public int? tierLevel; + public double costModifier; + } +} diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs index c6bee1950..60b63db8a 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs @@ -16,6 +16,8 @@ public class ProductionDataAccess : IProductionDataAccess private IDictionary _researchlevels; private ILookup _productionComponents; private IDictionary _productionDurations; + private IDictionary _productionCostByCategory; + private IDictionary _productionCostByTechLevel; private IDictionary _calibrationDefaults; private IDictionary _productionDecalibrations; @@ -26,8 +28,8 @@ public ProductionDataAccess(IEntityDefaultReader entityDefaultReader) 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,76 @@ 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); + + _productionCostByCategory = Database.CreateCache("productioncost", k.category, 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; + }, ProductionCostCategoryFilter); + + _productionCostByTechLevel = Database.CreateCache("productioncost", k.tierLevel, 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; + }, ProductionCostTechFilter); + } + + public bool ProductionCostCategoryFilter(IDataRecord record) + { + long? categoryFlag = record.GetValue(k.category); + if (categoryFlag == null) + return false; + CategoryFlags flag = (CategoryFlags)categoryFlag; + return flag.IsCategoryExists(); + } + + public bool ProductionCostTechFilter(IDataRecord record) + { + int? level = record.GetValue(k.tierLevel); + if (level == null) + return false; + return level > 0; } public bool ItemResearchLevelFilter(IDataRecord record) @@ -77,12 +131,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 ProductionCostByCategory => _productionCostByCategory; + public IDictionary ProductionCostByTechLevel => _productionCostByTechLevel; public IDictionary CalibrationDefaults => _calibrationDefaults; public ProductionDecalibration GetDecalibration(int targetDefinition) diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs index 1c7d13f44..0bb609879 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs @@ -2,9 +2,6 @@ using Perpetuum.EntityFramework; using Perpetuum.Log; using Perpetuum.Services.ProductionEngine.CalibrationPrograms; -using Perpetuum.ExportedTypes; -using System; -using System.Collections.Generic; namespace Perpetuum.Services.ProductionEngine { @@ -74,16 +71,6 @@ public static double GetProductionDuration(this IProductionDataAccess dataAccess return 1.0; } - - //TODO fixme: this is bad - private static readonly double[] TechLevelMods = { 1, 2, 3, 4 }; - private static readonly double[] BotClassMods = { 1, 2, 3, 4 }; - //This is worse - private static IEnumerable lights = new List() { CategoryFlags.cf_thelodica_runners, CategoryFlags.cf_nuimqol_runners, CategoryFlags.cf_pelistal_runners, CategoryFlags.cf_syndicate_runners, CategoryFlags.cf_industrial_runners}; - private static IEnumerable assaults = new List() { CategoryFlags.cf_thelodica_crawlers, CategoryFlags.cf_nuimqol_crawlers, CategoryFlags.cf_pelistal_crawlers, CategoryFlags.cf_syndicate_crawlers, CategoryFlags.cf_industrial_crawlers}; - private static IEnumerable mechs = new List() { CategoryFlags.cf_thelodica_mechs, CategoryFlags.cf_nuimqol_mechs, CategoryFlags.cf_pelistal_mechs, CategoryFlags.cf_syndicate_mechs, CategoryFlags.cf_industrial_mechs}; - private static IEnumerable heavies = new List() { CategoryFlags.cf_thelodica_heavymechs, CategoryFlags.cf_nuimqol_heavymechs, CategoryFlags.cf_pelistal_heavymechs, CategoryFlags.cf_syndicate_heavymechs, CategoryFlags.cf_industrial_heavymechs, CategoryFlags.cf_industrial_heavy_gliders}; - //Compute price modifier based on category of entity public static double GetProductionPriceModifier(this IProductionDataAccess dataAccess, int targetDefinition) { if (!EntityDefault.TryGet(targetDefinition, out EntityDefault ed)) @@ -94,32 +81,18 @@ public static double GetProductionPriceModifier(this IProductionDataAccess dataA var modifier = 1.0; - if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, lights)) - { - modifier = BotClassMods[0]; - } - else if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, assaults)) - { - modifier = BotClassMods[1]; - } - else if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, mechs)) + if (dataAccess.ProductionCostByCategory.TryGetValue(ed.CategoryFlags, out ProductionCost costByCat)) { - modifier = BotClassMods[2]; + modifier = costByCat.costModifier; } - else if (CategoryFlagsExtensions.IsAny(ed.CategoryFlags, heavies)) + else if (dataAccess.ProductionCostByTechLevel.TryGetValue(ed.Tier.level, out ProductionCost costByLevel)) { - modifier = BotClassMods[3]; - } - else - { - var techLevel = Math.Min(Math.Max(1, ed.Tier.level) - 1, TechLevelMods.Length - 1); - modifier = TechLevelMods[techLevel]; + modifier = costByLevel.costModifier; } 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); From 86ee548a85ad76d079f37135d0bfc9fca7f5e770 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 1 May 2020 16:56:15 -0400 Subject: [PATCH 161/457] add fluxore to enum --- src/Perpetuum/Zones/Terrains/Materials/MaterialType.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From f4f50c33fe651fa63ed85624a5027886e56ed405 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 1 May 2020 18:16:28 -0400 Subject: [PATCH 162/457] hide from mission ore types --- .../MissionEngine/MissionDataCacheObjects/MissionDataCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) { From 65350f99249b9386e00260d279d95dcb70dd7631 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 2 May 2020 00:42:15 -0400 Subject: [PATCH 163/457] keepout logic for epi and flux, lil refactor --- src/Perpetuum/Zones/DistanceConstants.cs | 2 +- .../Generators/MineralNodeGeneratorBase.cs | 46 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 80399a495..7c7847fac 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -36,7 +36,7 @@ public class DistanceConstants public const double TERRAFORM_MIN_RANGE_FROM_OBJECTS = 25.0; public const double TERRAFORM_BUOY_RADIUS = 15; - public const double MINERAL_DISTANCE_FROM_BASE_MIN = 75; + public const double MINERAL_DISTANCE_FROM_BASE_MIN = 100; public const double EXCLUDED_TELEPORT_RANGE = 200; public const double MOBILE_TELEPORT_MIN_DISTANCE_TO_DOCKINGBASE = 50; diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs index 69ad95a10..9f0be8fae 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs @@ -75,16 +75,6 @@ protected bool IsValid(Point location) return true; } - private bool IsInRangeOfBaseOrTeleports(Point location) - { - if (_zone.Units.OfType().WithinRange(location.ToPosition(), DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN).Any()) - return true; - - if (_zone.Units.OfType().WithinRange(location.ToPosition(), DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN).Any()) - return true; - return false; - } - private MineralNode CreateMineralNode(MineralLayer layer,Dictionary tiles) { int minx = int.MaxValue, miny = int.MaxValue, maxx = 0, maxy = 0; @@ -116,6 +106,29 @@ private MineralNode CreateMineralNode(MineralLayer layer,Dictionary().WithinRange(location.ToPosition(), dist).Any()) + return true; + + if (_zone.Units.OfType().WithinRange(location.ToPosition(), dist).Any()) + return true; + return false; + } + + private bool CheckSpecialOreKeepOuts(MineralLayer layer, Position startPosition) + { + if (layer.Configuration.Type == MaterialType.Epriton) + { + return IsInRangeOfBaseOrTeleports(startPosition, DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN); + } + else if (layer.Configuration.Type == MaterialType.FluxOre) + { + return IsInRangeOfBaseOrTeleports(startPosition, DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN * 2.0); + } + return false; + } + private Position FindStartPosition(MineralLayer layer) { var finder = new RandomPassablePositionFinder(_zone); @@ -125,23 +138,18 @@ private Position FindStartPosition(MineralLayer layer) if (!finder.Find(out Position startPosition)) continue; - if ( !IsValid(startPosition) ) + if (!IsValid(startPosition)) continue; - if( layer.Configuration.Type == MaterialType.Epriton) - { - if (IsInRangeOfBaseOrTeleports(startPosition)) - { - continue; - } - } + if (CheckSpecialOreKeepOuts(layer, startPosition)) + continue; var n = layer.GetNearestNode(startPosition); if (n == null) return startPosition; var d = n.Area.Distance(startPosition); - if (d < Radius*2) + if (d < Radius * 2) { // ha tul kozel van akkor keresunk ujat continue; From d39d9b4ab21d726c50a692a573d0f0cb623def43 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 5 May 2020 23:56:20 -0400 Subject: [PATCH 164/457] initial prototyping for ore npc spawning system --- .../PerpetuumBootstrapper.cs | 10 ++- src/Perpetuum/Perpetuum.csproj | 2 + .../EventMessages/NpcSpawnMessage.cs | 39 ++++++++ .../EventProcessors/OreNPCSpawner.cs | 90 +++++++++++++++++++ .../NpcSystem/Presences/DynamicPresence.cs | 38 ++++++++ .../Materials/Minerals/GravelLayer.cs | 5 +- .../Materials/Minerals/LiquidLayer.cs | 5 +- .../Materials/Minerals/MineralLayer.cs | 30 ++++++- .../Materials/Minerals/MineralNode.cs | 29 ++++++ .../Terrains/Materials/Minerals/OreLayer.cs | 5 +- src/Perpetuum/Zones/ZoneExtensions.Npc.cs | 13 +++ 11 files changed, 254 insertions(+), 12 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 73435d1b5..db97dcb83 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1492,6 +1492,7 @@ public void RegisterNpcs() }); RegisterPresence(PresenceType.DynamicPool); RegisterPresence(PresenceType.Dynamic); + RegisterPresence(PresenceType.Dynamic); RegisterPresence(PresenceType.Random); RegisterPresence(PresenceType.Roaming); RegisterPresence(PresenceType.FreeRoaming); @@ -1677,6 +1678,7 @@ private void InitRelayManager() _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(2.5))); @@ -2233,6 +2235,9 @@ private void RegisterTerrains() var ctx = x.Resolve(); return zone => { + var listener = new OreNPCSpawner(zone); + var eventListenerService = ctx.Resolve(); + eventListenerService.AttachListener(listener); if (zone is TrainingZone) { var repo = ctx.Resolve(); @@ -2249,19 +2254,18 @@ private void RegisterTerrains() foreach (var configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) { var 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); + var 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); + var layer = new LiquidLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory, eventListenerService); layer.LoadMineralNodes(); materialLayers.Add(layer); break; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 3f4f3940e..3bcab2364 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -291,12 +291,14 @@ + + diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs new file mode 100644 index 000000000..bbc04daa5 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs @@ -0,0 +1,39 @@ +using Perpetuum.Zones.Terrains.Materials.Minerals; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public enum OreNodeState + { + Updated, + Removed + } + + public class OreNpcSpawnMessage : EventMessage + { + private int _zoneId; + private MineralNode _node; + private OreNodeState _state; + + public OreNpcSpawnMessage(MineralNode node, int zoneID, OreNodeState state) + { + _zoneId = zoneID; + _node = node; + _state = state; + } + + public OreNodeState GetOreNodeState() + { + return _state; + } + + public int GetZoneID() + { + return _zoneId; + } + + public MineralNode GetMineralNode() + { + return _node; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs new file mode 100644 index 000000000..d0a110573 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -0,0 +1,90 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.Terrains.Materials.Minerals; +using System; +using System.Collections.Generic; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + /// + /// + public class OreNPCSpawner : EventProcessor + { + private IZone _zone; + private readonly IDictionary orePresences = new Dictionary(); + private ulong _lastTotal; + + + public OreNPCSpawner(IZone zone) + { + _zone = zone; + } + + private KeyValuePair FindEntryByValue(Presence presence) + { + foreach (var pair in orePresences) + { + if (pair.Value == presence) + { + return pair; + } + } + return new KeyValuePair(null, null); + } + + private bool IsEntryNull(KeyValuePair pair) + { + return pair.Key == null; + } + + private void OnPresenceExpired(Presence presence) + { + var pair = FindEntryByValue(presence); + if (IsEntryNull(pair)) + { + return; + } + pair.Value.PresenceExpired -= OnPresenceExpired; + orePresences.Remove(pair.Key); + } + + private void RemoveEntry(MineralNode node) + { + if (orePresences.ContainsKey(node)) + { + if (orePresences[node] != null) + { + orePresences[node].PresenceExpired -= OnPresenceExpired; + } + orePresences.Remove(node); + } + } + + public override void OnNext(EventMessage value) + { + if (value is OreNpcSpawnMessage msg && _zone.Id == msg.GetZoneID()) + { + var node = msg.GetMineralNode(); + if (orePresences.ContainsKey(node)) + { + if (msg.GetOreNodeState() == OreNodeState.Removed) + { + //Node has been removed from zone - remove from our cache + RemoveEntry(node); + } + //There is an active presence + return; + } + var current = node.GetTotalAmount(); + var spawnPos = _zone.FindPassablePointInRadius(node.Area.Center.ToPosition(), 20); + var pres = _zone.AddDynamicPresenceToPosition(1017, spawnPos, TimeSpan.FromSeconds(60)); + pres.PresenceExpired += OnPresenceExpired; + orePresences.Add(node, pres); + _zone.CreateBeam(BeamType.teleport_storm, b => b.WithPosition(spawnPos).WithDuration(TimeSpan.FromSeconds(100))); + } + + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs index 47c0b1bb6..69c23a00b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs @@ -56,4 +56,42 @@ protected virtual void OnPresenceExpired() PresenceExpired?.Invoke(this); } } + + + public class DynamicPresenceExtended : DynamicPresence + { + 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/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..64aaa6403 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.Services.EventServices.EventProcessors; 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; @@ -134,7 +139,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 +162,20 @@ public void RemoveNode(MineralNode node) }); } + private bool IsNPCSpawningOre(MineralNode node) + { + return node.Type == MaterialType.FluxOre; + } + + private void OnNodeDecrease(MineralNode node) + { + if (IsNPCSpawningOre(node)) + { + var msg = new OreNpcSpawnMessage(node, Configuration.ZoneId, OreNodeState.Updated); + _eventChannel.PublishMessage(msg); + } + } + private void OnNodeUpdated(MineralNode node) { RunAction(new SaveMineralNode(node)); @@ -164,6 +183,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 +247,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..bec06c886 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.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/ZoneExtensions.Npc.cs b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs index ccfce3928..177fb49b8 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.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; +using System; namespace Perpetuum.Zones { @@ -14,5 +16,16 @@ 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 position, TimeSpan timeSpan) + { + var presence = zone.PresenceManager.CreatePresence(presenceID).ThrowIfNotType(ErrorCodes.ItemNotFound); + presence.LifeTime = timeSpan; + presence.DynamicPosition = position; + presence.LoadFlocks(); + presence.Flocks.SpawnAllMembers(); + zone.PresenceManager.AddPresence(presence); + return presence; + } } } From ba51436ce7380cb2904c636973b797ee331cfc6c Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 8 May 2020 23:34:29 -0400 Subject: [PATCH 165/457] basic datamodel and lookup for presences based on ore% of field consumed --- .../PerpetuumBootstrapper.cs | 8 ++- src/Perpetuum/Perpetuum.csproj | 4 ++ .../EventMessages/NpcSpawnMessage.cs | 6 +- .../EventProcessors/OreNPCSpawner.cs | 21 +++++-- .../OreNPCSpawns/IOreNPCRepository.cs | 9 +++ .../NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs | 13 ++++ .../OreNPCSpawns/OreNPCRepository.cs | 58 +++++++++++++++++ .../NpcSystem/OreNPCSpawns/OreNPCSpawn.cs | 63 +++++++++++++++++++ 8 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index db97dcb83..273441ff9 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -126,6 +126,7 @@ using Perpetuum.Zones.Intrusion; using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.OreNPCSpawns; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; @@ -1424,6 +1425,8 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { + _builder.RegisterType().SingleInstance().As(); + _builder.RegisterType().As(); _builder.RegisterType(); _builder.RegisterType().Named>("directFlockIDGenerator").SingleInstance().WithParameter("startID",25000); @@ -2235,7 +2238,8 @@ private void RegisterTerrains() var ctx = x.Resolve(); return zone => { - var listener = new OreNPCSpawner(zone); + var reader = ctx.Resolve(); + var listener = new OreNPCSpawner(zone, ctx.Resolve(), reader); var eventListenerService = ctx.Resolve(); eventListenerService.AttachListener(listener); if (zone is TrainingZone) @@ -2248,7 +2252,7 @@ private void RegisterTerrains() } var nodeGeneratorFactory = new MineralNodeGeneratorFactory(zone); - var reader = ctx.Resolve(); + var materialLayers = new List(); foreach (var configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 3bcab2364..7242d4a6f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -678,6 +678,10 @@ + + + + diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs index bbc04daa5..8c54eff88 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs @@ -10,9 +10,9 @@ public enum OreNodeState public class OreNpcSpawnMessage : EventMessage { - private int _zoneId; - private MineralNode _node; - private OreNodeState _state; + private readonly int _zoneId; + private readonly MineralNode _node; + private readonly OreNodeState _state; public OreNpcSpawnMessage(MineralNode node, int zoneID, OreNodeState state) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index d0a110573..404865cf2 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -1,25 +1,30 @@ using Perpetuum.ExportedTypes; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Zones; +using Perpetuum.Zones.NpcSystem.OreNPCSpawns; 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 { /// + /// EventListener for each zone, receives messages for mineralnode mined /// public class OreNPCSpawner : EventProcessor { private IZone _zone; private readonly IDictionary orePresences = new Dictionary(); - private ulong _lastTotal; + private readonly IOreNPCRepository _oreNPCRepository; + private readonly IEnumerable _mineralConfigs; - - public OreNPCSpawner(IZone zone) + public OreNPCSpawner(IZone zone, IOreNPCRepository oreNPCRepository, IMineralConfigurationReader mineralConfigurationReader) { _zone = zone; + _oreNPCRepository = oreNPCRepository; + _mineralConfigs = mineralConfigurationReader.ReadAll().Where(c => c.ZoneId == zone.Id); } private KeyValuePair FindEntryByValue(Presence presence) @@ -77,9 +82,13 @@ public override void OnNext(EventMessage value) //There is an active presence return; } - var current = node.GetTotalAmount(); - var spawnPos = _zone.FindPassablePointInRadius(node.Area.Center.ToPosition(), 20); - var pres = _zone.AddDynamicPresenceToPosition(1017, spawnPos, TimeSpan.FromSeconds(60)); + var current = Convert.ToInt32(node.GetTotalAmount()); + var total = _mineralConfigs.Where(c => c.Type == node.Type).First().TotalAmountPerNode; + var percent = (current / (double)total).Clamp(0.0, 1.0); + var radius = (int)(node.Area.Diagonal / 2.0); + var spawnPos = _zone.FindPassablePointInRadius(node.Area.Center.ToPosition(), radius); + var presenceID = _oreNPCRepository.GetPresenceForOreAndThreshold(node.Type, percent); + var pres = _zone.AddDynamicPresenceToPosition(presenceID, spawnPos, TimeSpan.FromSeconds(30)); pres.PresenceExpired += OnPresenceExpired; orePresences.Add(node, pres); _zone.CreateBeam(BeamType.teleport_storm, b => b.WithPosition(spawnPos).WithDuration(TimeSpan.FromSeconds(100))); diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs new file mode 100644 index 000000000..8417b3c92 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs @@ -0,0 +1,9 @@ +using Perpetuum.Zones.Terrains.Materials; + +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +{ + public interface IOreNPCRepository + { + int GetPresenceForOreAndThreshold(MaterialType materialType, double threshold); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs new file mode 100644 index 000000000..bda5aae0a --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +{ + public interface IOreNPCSpawn + { + int GetPresenceFor(double percentageOfFieldConsumed); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs new file mode 100644 index 000000000..1681f9360 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs @@ -0,0 +1,58 @@ +using Perpetuum.Data; +using Perpetuum.Zones.Terrains.Materials; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + + +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +{ + public class OreNPCRepository : IOreNPCRepository + { + private readonly Lazy> _cache; + + public OreNPCRepository() + { + _cache = new Lazy>(InitCache); + } + + private IDictionary InitCache() + { + var records = Db.Query().CommandText("SELECT materialType, presenceId, threshold FROM npcorespawn") + .Execute() + .Select(CreatePresenceThresholdRecords).ToArray(); + var dict = new Dictionary>(); + foreach (var record in records) + { + if (!dict.ContainsKey(record.OreType)) + { + dict[record.OreType] = new List(); + } + dict[record.OreType].Add(record); + } + var cacheDict = new Dictionary(); + foreach (KeyValuePair> entry in dict) + { + cacheDict[entry.Key] = new OreNPCSpawn(entry.Key, entry.Value.ToArray()); + } + return cacheDict; + } + + public int GetPresenceForOreAndThreshold(MaterialType materialType, double threshold) + { + return _cache.Value[materialType].GetPresenceFor(threshold); + } + + private static PresenceThreshold CreatePresenceThresholdRecords(IDataRecord record) + { + var materialID = record.GetValue("materialType"); + var material = (MaterialType)materialID; + var presence = record.GetValue("presenceId"); + var threshold = record.GetValue("threshold"); + var pair = new PresenceThreshold(material, presence, threshold); + return pair; + } + + } +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs new file mode 100644 index 000000000..8194dbee4 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs @@ -0,0 +1,63 @@ +using Perpetuum.Zones.Terrains.Materials; +using System; +using System.Linq; +using System.Text; + +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +{ + public class PresenceThreshold + { + public MaterialType OreType { get; private set; } + public int Presence { get; private set; } + public double Threshold { get; private set; } + public PresenceThreshold(MaterialType oreType, int presenceID, double threshold) + { + OreType = oreType; + Presence = presenceID; + Threshold = threshold; + } + + public override string ToString() + { + return $"{Threshold}:{Presence}"; + } + } + + public class OreNPCSpawn : IOreNPCSpawn + { + //Sorted array of presence threhsold pairs + private PresenceThreshold[] _presences; + public MaterialType OreType { get; private set; } + + public OreNPCSpawn(MaterialType oreType, PresenceThreshold[] presences) + { + OreType = oreType; + _presences = presences.OrderBy(s => Array.IndexOf(presences, s.Threshold)).ToArray(); + } + + public int GetPresenceFor(double percentageOfFieldConsumed) + { + for (var i = 0; i < _presences.Length; i++) + { + if(_presences[i].Threshold > percentageOfFieldConsumed) + { + return _presences[i].Presence; + } + } + return -1; // Can not find presence + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine("OreNPCSpawn {"); + for(var i =0; i<_presences.Length; i++) + { + sb.AppendLine(_presences[i].ToString()); + } + sb.AppendLine("}"); + return sb.ToString(); + } + + } +} From dbee737061cce9da8c0f63dca4469b8b9710de6c Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 9 May 2020 23:47:58 -0400 Subject: [PATCH 166/457] major refactors, track spawned state of each wave, add new flock/presence to allow remote spawn location to homeposition --- .../PerpetuumBootstrapper.cs | 9 +- src/Perpetuum/Perpetuum.csproj | 11 +- .../EventProcessors/OreNPCSpawner.cs | 131 +++++++++++++----- src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 7 +- .../NpcSystem/Flocks/RemoteSpawningFlock.cs | 23 +++ .../OreNPCSpawns/IOreNPCRepository.cs | 7 +- .../NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs | 20 +-- .../NpcSystem/OreNPCSpawns/IOreNpcData.cs | 15 ++ .../OreNPCSpawns/OreNPCRepository.cs | 42 +----- .../NpcSystem/OreNPCSpawns/OreNPCSpawn.cs | 41 ++---- .../NpcSystem/OreNPCSpawns/OreNpcData.cs | 23 +++ .../NpcSystem/Presences/DynamicPresence.cs | 15 +- .../Zones/NpcSystem/Presences/PresenceType.cs | 3 +- src/Perpetuum/Zones/ZoneExtensions.Npc.cs | 5 +- 14 files changed, 224 insertions(+), 128 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNpcData.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 273441ff9..49e7aed65 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1425,7 +1425,7 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { - _builder.RegisterType().SingleInstance().As(); + _builder.RegisterType().SingleInstance().As(); _builder.RegisterType().As(); _builder.RegisterType(); @@ -1482,6 +1482,7 @@ public void RegisterNpcs() RegisterFlock(PresenceType.Direct); RegisterFlock(PresenceType.DynamicPool); RegisterFlock(PresenceType.Dynamic); + RegisterFlock(PresenceType.DynamicExtended); RegisterFlock(PresenceType.Random); RegisterFlock(PresenceType.Roaming); RegisterFlock(PresenceType.FreeRoaming); @@ -1495,7 +1496,7 @@ public void RegisterNpcs() }); RegisterPresence(PresenceType.DynamicPool); RegisterPresence(PresenceType.Dynamic); - RegisterPresence(PresenceType.Dynamic); + RegisterPresence(PresenceType.DynamicExtended); RegisterPresence(PresenceType.Random); RegisterPresence(PresenceType.Roaming); RegisterPresence(PresenceType.FreeRoaming); @@ -1681,7 +1682,7 @@ private void InitRelayManager() _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); - _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(2.5))); @@ -2239,7 +2240,7 @@ private void RegisterTerrains() return zone => { var reader = ctx.Resolve(); - var listener = new OreNPCSpawner(zone, ctx.Resolve(), reader); + var listener = new OreNpcSpawner(zone, ctx.Resolve(), reader); var eventListenerService = ctx.Resolve(); eventListenerService.AttachListener(listener); if (zone is TrainingZone) diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 7242d4a6f..edd338132 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -677,11 +677,14 @@ + - - - - + + + + + + diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index 404865cf2..ebb716412 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -1,58 +1,48 @@ using Perpetuum.ExportedTypes; +using Perpetuum.Log; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Zones; +using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.NpcSystem.OreNPCSpawns; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.Terrains.Materials.Minerals; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; +using System.Threading.Tasks; namespace Perpetuum.Services.EventServices.EventProcessors { /// /// EventListener for each zone, receives messages for mineralnode mined + /// TODO need to track foreach node: is there a current npc, is there a new npc to be spawned /// - public class OreNPCSpawner : EventProcessor + public class OreNpcSpawner : EventProcessor { - private IZone _zone; + private readonly int SPAWN_DIST_FROM_FIELD = 50; + + private readonly IZone _zone; private readonly IDictionary orePresences = new Dictionary(); - private readonly IOreNPCRepository _oreNPCRepository; + private readonly IDictionary oreSpawns = new Dictionary(); + private readonly IOreNpcRepository _oreNPCRepository; private readonly IEnumerable _mineralConfigs; - public OreNPCSpawner(IZone zone, IOreNPCRepository oreNPCRepository, IMineralConfigurationReader mineralConfigurationReader) + public OreNpcSpawner(IZone zone, IOreNpcRepository oreNPCRepository, IMineralConfigurationReader mineralConfigurationReader) { _zone = zone; _oreNPCRepository = oreNPCRepository; _mineralConfigs = mineralConfigurationReader.ReadAll().Where(c => c.ZoneId == zone.Id); } - private KeyValuePair FindEntryByValue(Presence presence) - { - foreach (var pair in orePresences) - { - if (pair.Value == presence) - { - return pair; - } - } - return new KeyValuePair(null, null); - } - - private bool IsEntryNull(KeyValuePair pair) - { - return pair.Key == null; - } - private void OnPresenceExpired(Presence presence) { - var pair = FindEntryByValue(presence); - if (IsEntryNull(pair)) + var matchedEntries = orePresences.Where(p => p.Value.Equals(presence)).ToList(); + foreach (var pair in matchedEntries) { - return; + pair.Value.PresenceExpired -= OnPresenceExpired; + orePresences.Remove(pair.Key); } - pair.Value.PresenceExpired -= OnPresenceExpired; - orePresences.Remove(pair.Key); } private void RemoveEntry(MineralNode node) @@ -67,11 +57,54 @@ private void RemoveEntry(MineralNode node) } } + private Position FindSpawnLocation(Position start, double range) + { + var p = new Position(); + List result = null; + for (int i = 0; i < 10; i++) + { + var random = FastRandom.NextDouble(0.0, 1.0); + var pos = start.OffsetInDirection(random, range); + var posFinder = new ClosestWalkablePositionFinder(_zone, pos); + posFinder.Find(out p); + result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), 100); + if (result != null) + { + break; + } + } + if (result == null) + { + return Position.Empty; + } + return p; + } + + private double ComputeFieldPercentConsumed(MineralNode node) + { + var current = Convert.ToInt32(node.GetTotalAmount()); + var total = _mineralConfigs.Where(c => c.Type == node.Type).First().TotalAmountPerNode; + var percent = 1.0 - (current / (double)total).Clamp(); + return percent; + } + + private bool _spawning = false; + public override void OnNext(EventMessage value) { if (value is OreNpcSpawnMessage msg && _zone.Id == msg.GetZoneID()) { + if (_spawning) + { + return; + } var node = msg.GetMineralNode(); + if (!oreSpawns.ContainsKey(node)) + { + var oreSpawn = _oreNPCRepository.CreateOreNPCSpawn(node.Type); + oreSpawns.Add(node, oreSpawn); + } + Logger.Info(oreSpawns[node].ToString()); if (orePresences.ContainsKey(node)) { if (msg.GetOreNodeState() == OreNodeState.Removed) @@ -82,18 +115,42 @@ public override void OnNext(EventMessage value) //There is an active presence return; } - var current = Convert.ToInt32(node.GetTotalAmount()); - var total = _mineralConfigs.Where(c => c.Type == node.Type).First().TotalAmountPerNode; - var percent = (current / (double)total).Clamp(0.0, 1.0); - var radius = (int)(node.Area.Diagonal / 2.0); - var spawnPos = _zone.FindPassablePointInRadius(node.Area.Center.ToPosition(), radius); - var presenceID = _oreNPCRepository.GetPresenceForOreAndThreshold(node.Type, percent); - var pres = _zone.AddDynamicPresenceToPosition(presenceID, spawnPos, TimeSpan.FromSeconds(30)); - pres.PresenceExpired += OnPresenceExpired; - orePresences.Add(node, pres); - _zone.CreateBeam(BeamType.teleport_storm, b => b.WithPosition(spawnPos).WithDuration(TimeSpan.FromSeconds(100))); - } + var fieldCenter = node.Area.Center.ToPosition(); + var spawnPos = FindSpawnLocation(fieldCenter, SPAWN_DIST_FROM_FIELD); + if (spawnPos == Position.Empty) + { + return; // Failed to find valid spawn location, try again on next cycle + } + var percent = ComputeFieldPercentConsumed(node); + var orePresence = oreSpawns[node].GetNextPresence(percent); + if (orePresence == null) + { + return; // Presence already spawned once, or not found + } + var delay = TimeSpan.FromSeconds(10); + _zone.CreateBeam(BeamType.npc_egg_beam, b => b.WithPosition(fieldCenter).WithDuration(delay)); + _zone.CreateBeam(BeamType.teleport_storm, b => b.WithPosition(fieldCenter).WithDuration(delay)); + _spawning = true; + Task.Delay(delay).ContinueWith(t => + { + try + { + var pres = _zone.AddDynamicPresenceToPosition(orePresence.Presence, fieldCenter, spawnPos, TimeSpan.FromSeconds(180)); // TODO timeout to 3 hr + orePresence.Spawned = true; + pres.PresenceExpired += OnPresenceExpired; + orePresences.Add(node, pres); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + finally + { + _spawning = false; + } + }); + } } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index 33158bc73..8321725ef 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -147,7 +147,7 @@ protected virtual void CreateMemberInZone() Log($"member spawned to zone:{zone.Id} EID:{npc.Eid}"); } - private Position GetSpawnPosition(Position spawnOrigin) + protected virtual Position GetSpawnPosition(Position spawnOrigin) { var spawnRangeMin = Configuration.SpawnRange.Min; var spawnRangeMax = Configuration.SpawnRange.Max.Min(HomeRange); @@ -170,6 +170,11 @@ private NpcBehavior GetBehavior() { return NpcBehavior.Create(Configuration.BehaviorType); } + else if(Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresenceExtended) + { + // This is needed to prevent the super class from matching the next condition, which overrides the behaviour type! + return NpcBehavior.Create(Configuration.BehaviorType); + } else if (Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresence) { // hogy ne tamadjanak be mindenkit rogton diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs new file mode 100644 index 000000000..4a451312e --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs @@ -0,0 +1,23 @@ +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 : NormalFlock + { + public RemoteSpawningFlock(IFlockConfiguration configuration, Presence presence) : base(configuration, presence) + { + } + + protected override Position GetSpawnPosition(Position spawnOrigin) + { + if (Presence is DynamicPresenceExtended presence) + { + return presence.SpawnLocation; + } + return base.GetSpawnPosition(spawnOrigin); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs index 8417b3c92..e59a5468a 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs @@ -2,8 +2,11 @@ namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns { - public interface IOreNPCRepository + /// + /// DB lookup interface and factory for IOreNPCSpawns + /// + public interface IOreNpcRepository { - int GetPresenceForOreAndThreshold(MaterialType materialType, double threshold); + IOreNpcSpawn CreateOreNPCSpawn(MaterialType materialType); } } diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs index bda5aae0a..1acd3d8b2 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs @@ -1,13 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns { - public interface IOreNPCSpawn + /// + /// Ore NPC Spawn - a list of presences that spawn at particular levels of field depletion + /// + public interface IOreNpcSpawn { - int GetPresenceFor(double percentageOfFieldConsumed); + /// + /// Gets the next IOreNpcData for the provided threshold percent mined + /// + /// double of range [0.-1.] representing a percentage of ore field mined + /// IOreNpcData + IOreNpcData GetNextPresence(double percentageOfFieldConsumed); } } diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs new file mode 100644 index 000000000..1f378ab8c --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs @@ -0,0 +1,15 @@ +using Perpetuum.Zones.Terrains.Materials; + +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +{ + /// + /// Ore NPC spawn data that tracks spawned state for some Presence, Threshold, OreType tuple + /// + public interface IOreNpcData + { + MaterialType OreType { get; } + int Presence { get; } + double Threshold { get; } + bool Spawned { get; set; } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs index 1681f9360..3589c2777 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs @@ -1,56 +1,28 @@ using Perpetuum.Data; using Perpetuum.Zones.Terrains.Materials; -using System; -using System.Collections.Generic; using System.Data; using System.Linq; - namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns { - public class OreNPCRepository : IOreNPCRepository + public class OreNpcRepository : IOreNpcRepository { - private readonly Lazy> _cache; - - public OreNPCRepository() - { - _cache = new Lazy>(InitCache); - } - - private IDictionary InitCache() + public IOreNpcSpawn CreateOreNPCSpawn(MaterialType materialType) { - var records = Db.Query().CommandText("SELECT materialType, presenceId, threshold FROM npcorespawn") + var records = Db.Query().CommandText("SELECT materialType, presenceId, threshold FROM npcorespawn WHERE @material=materialType") + .SetParameter("@material", materialType) .Execute() .Select(CreatePresenceThresholdRecords).ToArray(); - var dict = new Dictionary>(); - foreach (var record in records) - { - if (!dict.ContainsKey(record.OreType)) - { - dict[record.OreType] = new List(); - } - dict[record.OreType].Add(record); - } - var cacheDict = new Dictionary(); - foreach (KeyValuePair> entry in dict) - { - cacheDict[entry.Key] = new OreNPCSpawn(entry.Key, entry.Value.ToArray()); - } - return cacheDict; - } - - public int GetPresenceForOreAndThreshold(MaterialType materialType, double threshold) - { - return _cache.Value[materialType].GetPresenceFor(threshold); + return new OreNpcSpawn(materialType, records); } - private static PresenceThreshold CreatePresenceThresholdRecords(IDataRecord record) + private static IOreNpcData CreatePresenceThresholdRecords(IDataRecord record) { var materialID = record.GetValue("materialType"); var material = (MaterialType)materialID; var presence = record.GetValue("presenceId"); var threshold = record.GetValue("threshold"); - var pair = new PresenceThreshold(material, presence, threshold); + var pair = new OreNpcData(material, presence, threshold); return pair; } diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs index 8194dbee4..507eb709a 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs @@ -5,59 +5,44 @@ namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns { - public class PresenceThreshold + public class OreNpcSpawn : IOreNpcSpawn { + // Sorted array of OreNpcData by threshold + private readonly IOreNpcData[] _presences; public MaterialType OreType { get; private set; } - public int Presence { get; private set; } - public double Threshold { get; private set; } - public PresenceThreshold(MaterialType oreType, int presenceID, double threshold) - { - OreType = oreType; - Presence = presenceID; - Threshold = threshold; - } - public override string ToString() - { - return $"{Threshold}:{Presence}"; - } - } - - public class OreNPCSpawn : IOreNPCSpawn - { - //Sorted array of presence threhsold pairs - private PresenceThreshold[] _presences; - public MaterialType OreType { get; private set; } - - public OreNPCSpawn(MaterialType oreType, PresenceThreshold[] presences) + public OreNpcSpawn(MaterialType oreType, IOreNpcData[] presences) { OreType = oreType; _presences = presences.OrderBy(s => Array.IndexOf(presences, s.Threshold)).ToArray(); } - public int GetPresenceFor(double percentageOfFieldConsumed) + public IOreNpcData GetNextPresence(double percentageOfFieldConsumed) { for (var i = 0; i < _presences.Length; i++) { - if(_presences[i].Threshold > percentageOfFieldConsumed) + if (_presences[i].Threshold > percentageOfFieldConsumed) { - return _presences[i].Presence; + if (_presences[i].Spawned) + { + return null; + } + return _presences[i]; } } - return -1; // Can not find presence + return null; } public override string ToString() { var sb = new StringBuilder(); sb.AppendLine("OreNPCSpawn {"); - for(var i =0; i<_presences.Length; i++) + 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/OreNPCSpawns/OreNpcData.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNpcData.cs new file mode 100644 index 000000000..098782007 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNpcData.cs @@ -0,0 +1,23 @@ +using Perpetuum.Zones.Terrains.Materials; + +namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +{ + public class OreNpcData : IOreNpcData + { + public MaterialType OreType { get; private set; } + public int Presence { get; private set; } + public double Threshold { get; private set; } + public bool Spawned { get; set; } + public OreNpcData(MaterialType oreType, int presenceID, double threshold) + { + OreType = oreType; + Presence = presenceID; + Threshold = threshold; + } + + public override string ToString() + { + return $"{Threshold}:{Presence} Spawned? {Spawned}"; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs index 69c23a00b..44e651da7 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs @@ -5,7 +5,7 @@ namespace Perpetuum.Zones.NpcSystem.Presences { - public class DynamicPresence : Presence,INotifyPresenceExpired + public class DynamicPresence : Presence, INotifyPresenceExpired { private TimeTracker _lifeTimeTracker = new TimeTracker(TimeSpan.FromHours(1)); @@ -19,10 +19,10 @@ public TimeSpan LifeTime public event Action PresenceExpired; - public DynamicPresence(IZone zone, IPresenceConfiguration configuration) : base(zone,configuration) + public DynamicPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { - if (Configuration.DynamicLifeTime != null) - LifeTime = TimeSpan.FromMilliseconds((int) Configuration.DynamicLifeTime); + if (Configuration.DynamicLifeTime != null) + LifeTime = TimeSpan.FromMilliseconds((int)Configuration.DynamicLifeTime); } public override Area Area @@ -57,9 +57,14 @@ protected virtual void OnPresenceExpired() } } - + /// + /// 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) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs index 9bfa6b5e7..a81fa9498 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs @@ -10,6 +10,7 @@ public enum PresenceType FreeRoaming = 5, Direct = 6, Interzone = 7, - InterzoneRoaming = 8 + InterzoneRoaming = 8, + DynamicExtended = 9 } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/ZoneExtensions.Npc.cs b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs index 177fb49b8..218e63c4b 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Npc.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs @@ -17,11 +17,12 @@ public static DynamicPresence AddDynamicPresenceToPosition(this IZone zone,int p return presence; } - public static DynamicPresenceExtended AddDynamicPresenceToPosition(this IZone zone, int presenceID, Position position, TimeSpan timeSpan) + 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 = position; + presence.DynamicPosition = homePos; + presence.SpawnLocation = startPos; presence.LoadFlocks(); presence.Flocks.SpawnAllMembers(); zone.PresenceManager.AddPresence(presence); From 236842f7ed8461116b8adde6b58062af868d2898 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sun, 10 May 2020 23:37:04 -0400 Subject: [PATCH 167/457] refactor oreNpcs to 'npcReinforcements' to make generic for future features --- .../PerpetuumBootstrapper.cs | 6 +-- src/Perpetuum/Perpetuum.csproj | 15 ++++---- ...cSpawnMessage.cs => OreNpcSpawnMessage.cs} | 0 .../EventProcessors/OreNPCSpawner.cs | 38 +++++++++---------- .../OreNPCSpawns/IOreNPCRepository.cs | 12 ------ .../NpcSystem/OreNPCSpawns/IOreNpcData.cs | 15 -------- .../OreNPCSpawns/OreNPCRepository.cs | 30 --------------- .../Reinforcements/INpcReinforcementWave.cs | 12 ++++++ .../INpcReinforcements.cs} | 6 +-- .../INpcReinforcementsRepository.cs | 13 +++++++ .../NpcReinforcementWave.cs} | 8 ++-- .../NpcReinforcements.cs} | 13 +++---- .../NpcReinforcementsRepository.cs | 33 ++++++++++++++++ .../Reinforcements/ReinforcementType.cs | 8 ++++ 14 files changed, 108 insertions(+), 101 deletions(-) rename src/Perpetuum/Services/EventServices/EventMessages/{NpcSpawnMessage.cs => OreNpcSpawnMessage.cs} (100%) delete mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs rename src/Perpetuum/Zones/NpcSystem/{OreNPCSpawns/IOreNPCSpawn.cs => Reinforcements/INpcReinforcements.cs} (71%) create mode 100644 src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs rename src/Perpetuum/Zones/NpcSystem/{OreNPCSpawns/OreNpcData.cs => Reinforcements/NpcReinforcementWave.cs} (61%) rename src/Perpetuum/Zones/NpcSystem/{OreNPCSpawns/OreNPCSpawn.cs => Reinforcements/NpcReinforcements.cs} (73%) create mode 100644 src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 49e7aed65..40a8ba93c 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -126,7 +126,7 @@ using Perpetuum.Zones.Intrusion; using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.NpcSystem.Flocks; -using Perpetuum.Zones.NpcSystem.OreNPCSpawns; +using Perpetuum.Zones.NpcSystem.Reinforcements; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; @@ -1425,7 +1425,7 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { - _builder.RegisterType().SingleInstance().As(); + _builder.RegisterType().SingleInstance().As(); _builder.RegisterType().As(); _builder.RegisterType(); @@ -2240,7 +2240,7 @@ private void RegisterTerrains() return zone => { var reader = ctx.Resolve(); - var listener = new OreNpcSpawner(zone, ctx.Resolve(), reader); + var listener = new OreNpcSpawner(zone, ctx.Resolve(), reader); var eventListenerService = ctx.Resolve(); eventListenerService.AttachListener(listener); if (zone is TrainingZone) diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index edd338132..06753af21 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -291,7 +291,7 @@ - + @@ -679,12 +679,12 @@ - - - - - - + + + + + + @@ -701,6 +701,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs similarity index 100% rename from src/Perpetuum/Services/EventServices/EventMessages/NpcSpawnMessage.cs rename to src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index ebb716412..aec282533 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -3,7 +3,7 @@ using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Zones; using Perpetuum.Zones.Finders.PositionFinders; -using Perpetuum.Zones.NpcSystem.OreNPCSpawns; +using Perpetuum.Zones.NpcSystem.Reinforcements; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.Terrains.Materials.Minerals; using System; @@ -23,37 +23,37 @@ public class OreNpcSpawner : EventProcessor private readonly int SPAWN_DIST_FROM_FIELD = 50; private readonly IZone _zone; - private readonly IDictionary orePresences = new Dictionary(); - private readonly IDictionary oreSpawns = new Dictionary(); - private readonly IOreNpcRepository _oreNPCRepository; + private readonly IDictionary spawnedPresences = new Dictionary(); + private readonly IDictionary reinforcementsByMineralNode = new Dictionary(); + private readonly INpcReinforcementsRepository _npcReinforcementsRepo; private readonly IEnumerable _mineralConfigs; - public OreNpcSpawner(IZone zone, IOreNpcRepository oreNPCRepository, IMineralConfigurationReader mineralConfigurationReader) + public OreNpcSpawner(IZone zone, INpcReinforcementsRepository reinforcementsRepo, IMineralConfigurationReader mineralConfigurationReader) { _zone = zone; - _oreNPCRepository = oreNPCRepository; + _npcReinforcementsRepo = reinforcementsRepo; _mineralConfigs = mineralConfigurationReader.ReadAll().Where(c => c.ZoneId == zone.Id); } private void OnPresenceExpired(Presence presence) { - var matchedEntries = orePresences.Where(p => p.Value.Equals(presence)).ToList(); + var matchedEntries = spawnedPresences.Where(p => p.Value.Equals(presence)).ToList(); foreach (var pair in matchedEntries) { pair.Value.PresenceExpired -= OnPresenceExpired; - orePresences.Remove(pair.Key); + spawnedPresences.Remove(pair.Key); } } private void RemoveEntry(MineralNode node) { - if (orePresences.ContainsKey(node)) + if (spawnedPresences.ContainsKey(node)) { - if (orePresences[node] != null) + if (spawnedPresences[node] != null) { - orePresences[node].PresenceExpired -= OnPresenceExpired; + spawnedPresences[node].PresenceExpired -= OnPresenceExpired; } - orePresences.Remove(node); + spawnedPresences.Remove(node); } } @@ -99,13 +99,13 @@ public override void OnNext(EventMessage value) return; } var node = msg.GetMineralNode(); - if (!oreSpawns.ContainsKey(node)) + if (!reinforcementsByMineralNode.ContainsKey(node)) { - var oreSpawn = _oreNPCRepository.CreateOreNPCSpawn(node.Type); - oreSpawns.Add(node, oreSpawn); + var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type); + reinforcementsByMineralNode.Add(node, oreSpawn); } - Logger.Info(oreSpawns[node].ToString()); - if (orePresences.ContainsKey(node)) + Logger.Info(reinforcementsByMineralNode[node].ToString()); + if (spawnedPresences.ContainsKey(node)) { if (msg.GetOreNodeState() == OreNodeState.Removed) { @@ -122,7 +122,7 @@ public override void OnNext(EventMessage value) return; // Failed to find valid spawn location, try again on next cycle } var percent = ComputeFieldPercentConsumed(node); - var orePresence = oreSpawns[node].GetNextPresence(percent); + var orePresence = reinforcementsByMineralNode[node].GetNextPresence(percent); if (orePresence == null) { return; // Presence already spawned once, or not found @@ -139,7 +139,7 @@ public override void OnNext(EventMessage value) var pres = _zone.AddDynamicPresenceToPosition(orePresence.Presence, fieldCenter, spawnPos, TimeSpan.FromSeconds(180)); // TODO timeout to 3 hr orePresence.Spawned = true; pres.PresenceExpired += OnPresenceExpired; - orePresences.Add(node, pres); + spawnedPresences.Add(node, pres); } catch (Exception ex) { diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs deleted file mode 100644 index e59a5468a..000000000 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Perpetuum.Zones.Terrains.Materials; - -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns -{ - /// - /// DB lookup interface and factory for IOreNPCSpawns - /// - public interface IOreNpcRepository - { - IOreNpcSpawn CreateOreNPCSpawn(MaterialType materialType); - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs deleted file mode 100644 index 1f378ab8c..000000000 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNpcData.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Perpetuum.Zones.Terrains.Materials; - -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns -{ - /// - /// Ore NPC spawn data that tracks spawned state for some Presence, Threshold, OreType tuple - /// - public interface IOreNpcData - { - MaterialType OreType { get; } - int Presence { get; } - double Threshold { get; } - bool Spawned { get; set; } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs b/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs deleted file mode 100644 index 3589c2777..000000000 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Perpetuum.Data; -using Perpetuum.Zones.Terrains.Materials; -using System.Data; -using System.Linq; - -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns -{ - public class OreNpcRepository : IOreNpcRepository - { - public IOreNpcSpawn CreateOreNPCSpawn(MaterialType materialType) - { - var records = Db.Query().CommandText("SELECT materialType, presenceId, threshold FROM npcorespawn WHERE @material=materialType") - .SetParameter("@material", materialType) - .Execute() - .Select(CreatePresenceThresholdRecords).ToArray(); - return new OreNpcSpawn(materialType, records); - } - - private static IOreNpcData CreatePresenceThresholdRecords(IDataRecord record) - { - var materialID = record.GetValue("materialType"); - var material = (MaterialType)materialID; - var presence = record.GetValue("presenceId"); - var threshold = record.GetValue("threshold"); - var pair = new OreNpcData(material, presence, threshold); - return pair; - } - - } -} \ 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..4ec3d111a --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs @@ -0,0 +1,12 @@ +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + /// + /// Represents a single npc presence and its spawned state + /// + public interface INpcReinforcementWave + { + int Presence { get; } + double Threshold { get; } + bool Spawned { get; set; } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs similarity index 71% rename from src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs rename to src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs index 1acd3d8b2..ff5ce377a 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/IOreNPCSpawn.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs @@ -1,15 +1,15 @@ -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +namespace Perpetuum.Zones.NpcSystem.Reinforcements { /// /// Ore NPC Spawn - a list of presences that spawn at particular levels of field depletion /// - public interface IOreNpcSpawn + public interface INpcReinforcements { /// /// Gets the next IOreNpcData for the provided threshold percent mined /// /// double of range [0.-1.] representing a percentage of ore field mined /// IOreNpcData - IOreNpcData GetNextPresence(double percentageOfFieldConsumed); + INpcReinforcementWave GetNextPresence(double percentageOfFieldConsumed); } } diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs new file mode 100644 index 000000000..c5b01a5ea --- /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); + INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo); + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNpcData.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs similarity index 61% rename from src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNpcData.cs rename to src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs index 098782007..75f6b0c98 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNpcData.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs @@ -1,16 +1,14 @@ using Perpetuum.Zones.Terrains.Materials; -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +namespace Perpetuum.Zones.NpcSystem.Reinforcements { - public class OreNpcData : IOreNpcData + public class NpcReinforcementWave : INpcReinforcementWave { - public MaterialType OreType { get; private set; } public int Presence { get; private set; } public double Threshold { get; private set; } public bool Spawned { get; set; } - public OreNpcData(MaterialType oreType, int presenceID, double threshold) + public NpcReinforcementWave(int presenceID, double threshold) { - OreType = oreType; Presence = presenceID; Threshold = threshold; } diff --git a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs similarity index 73% rename from src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs rename to src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs index 507eb709a..81b9c8377 100644 --- a/src/Perpetuum/Zones/NpcSystem/OreNPCSpawns/OreNPCSpawn.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs @@ -3,21 +3,20 @@ using System.Linq; using System.Text; -namespace Perpetuum.Zones.NpcSystem.OreNPCSpawns +namespace Perpetuum.Zones.NpcSystem.Reinforcements { - public class OreNpcSpawn : IOreNpcSpawn + public class NpcReinforcements : INpcReinforcements { // Sorted array of OreNpcData by threshold - private readonly IOreNpcData[] _presences; - public MaterialType OreType { get; private set; } + private readonly INpcReinforcementWave[] _presences; - public OreNpcSpawn(MaterialType oreType, IOreNpcData[] presences) + + public NpcReinforcements(INpcReinforcementWave[] presences) { - OreType = oreType; _presences = presences.OrderBy(s => Array.IndexOf(presences, s.Threshold)).ToArray(); } - public IOreNpcData GetNextPresence(double percentageOfFieldConsumed) + public INpcReinforcementWave GetNextPresence(double percentageOfFieldConsumed) { for (var i = 0; i < _presences.Length; i++) { diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs new file mode 100644 index 000000000..07c46c653 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs @@ -0,0 +1,33 @@ +using Perpetuum.Data; +using Perpetuum.Zones.Terrains.Materials; +using System.Data; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements +{ + public class NpcReinforcementsRepository : INpcReinforcementsRepository + { + public INpcReinforcements CreateOreNPCSpawn(MaterialType materialType) + { + var records = Db.Query().CommandText("SELECT threshold, presenceId from npcreinforcements WHERE targetId=@target AND reinforcementType=@type") + .SetParameter("@target", materialType) + .SetParameter("@type", ReinforcementType.Minerals) + .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) + { + throw new System.NotImplementedException(); //TODO new feature =) + } + } +} \ 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..346bc229a --- /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 + } +} \ No newline at end of file From 3bd64c7717e26d1efd1e7446d7eefd38f502ff0f Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 12 May 2020 01:03:38 -0400 Subject: [PATCH 168/457] fix bug, refactor --- .../Generators/MineralNodeGeneratorBase.cs | 51 ++++++++++++------- .../Materials/Minerals/MineralLayer.cs | 1 + 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs index 9f0be8fae..a3d8805f4 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/Generators/MineralNodeGeneratorBase.cs @@ -22,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; } @@ -40,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) { @@ -75,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; @@ -100,7 +102,7 @@ private MineralNode CreateMineralNode(MineralLayer layer,Dictionary().WithinRange(location.ToPosition(), dist).Any()) + if (_zone.Units.OfType().WithinRange2D(location.ToPosition(), dist).Any()) return true; - if (_zone.Units.OfType().WithinRange(location.ToPosition(), dist).Any()) + if (_zone.Units.OfType().WithinRange2D(location.ToPosition(), dist).Any()) return true; return false; } - private bool CheckSpecialOreKeepOuts(MineralLayer layer, Position startPosition) + private static double KeepOutDist(MaterialType material) { - if (layer.Configuration.Type == MaterialType.Epriton) - { - return IsInRangeOfBaseOrTeleports(startPosition, DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN); - } - else if (layer.Configuration.Type == MaterialType.FluxOre) + switch (material) { - return IsInRangeOfBaseOrTeleports(startPosition, DistanceConstants.MINERAL_DISTANCE_FROM_BASE_MIN * 2.0); + 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; } - return false; + } + + private static bool IsKeepOutMaterial(MaterialType material) + { + return material == MaterialType.Epriton || material == MaterialType.FluxOre; } private Position FindStartPosition(MineralLayer layer) @@ -138,10 +145,16 @@ private Position FindStartPosition(MineralLayer layer) if (!finder.Find(out Position startPosition)) continue; - if (!IsValid(startPosition)) - continue; + // Check keepout distances for special ore types + if (IsKeepOutMaterial(Material)) + { + if (IsInRangeOfBaseOrTeleports(startPosition, KeepOutDist(Material))) + { + continue; + } + } - if (CheckSpecialOreKeepOuts(layer, startPosition)) + if (!IsValid(startPosition)) continue; var n = layer.GetNearestNode(startPosition); diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs index 4d16ca556..9cf193b8f 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs @@ -71,6 +71,7 @@ public void GenerateNewNode() generator.MaxTiles = _configuration.MaxTilesPerNode; generator.TotalAmount = _configuration.TotalAmountPerNode; generator.MinThreshold = _configuration.MinThreshold; + generator.Material = _configuration.Type; RunAction(new GenerateMineralNode(generator)); } From 1b9dde56c4c595974bc8d1096f107e262ea9d53d Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 13 May 2020 18:18:13 -0400 Subject: [PATCH 169/457] throttle event messages to event queue --- .../PerpetuumBootstrapper.cs | 2 +- .../EventServices/EventListenerService.cs | 12 +++++++----- .../EventProcessors/OreNPCSpawner.cs | 2 +- .../Terrains/Materials/Minerals/MineralLayer.cs | 16 ++++++++++++---- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 40a8ba93c..cf20f3adb 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1685,7 +1685,7 @@ private void InitRelayManager() _builder.RegisterType(); _builder.RegisterType().SingleInstance().OnActivated(e => { - e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(2.5))); + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(0.75))); e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); }); diff --git a/src/Perpetuum/Services/EventServices/EventListenerService.cs b/src/Perpetuum/Services/EventServices/EventListenerService.cs index 8afca4c7d..16eb61f8c 100644 --- a/src/Perpetuum/Services/EventServices/EventListenerService.cs +++ b/src/Perpetuum/Services/EventServices/EventListenerService.cs @@ -12,8 +12,8 @@ namespace Perpetuum.Services.EventServices /// public class EventListenerService : Process { - private IList> _observers; - private ConcurrentQueue _queue; + private readonly IList> _observers; + private readonly ConcurrentQueue _queue; public EventListenerService() { @@ -47,12 +47,14 @@ public void AttachListener(EventProcessor observer) _observers.Add(observer); } - public override void Update(TimeSpan time) { - if (_queue.TryDequeue(out var message)) + while (!_queue.IsEmpty) { - NotifyListeners(message); + if (_queue.TryDequeue(out var message)) + { + NotifyListeners(message); + } } } diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index aec282533..0a5d1812f 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -20,7 +20,7 @@ namespace Perpetuum.Services.EventServices.EventProcessors /// public class OreNpcSpawner : EventProcessor { - private readonly int SPAWN_DIST_FROM_FIELD = 50; + private const int SPAWN_DIST_FROM_FIELD = 50; private readonly IZone _zone; private readonly IDictionary spawnedPresences = new Dictionary(); diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs index 40d59b2e2..7f2798dd5 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs @@ -5,7 +5,6 @@ using Perpetuum.Log; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; -using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Zones.Terrains.Materials.Minerals.Actions; using Perpetuum.Zones.Terrains.Materials.Minerals.Generators; @@ -168,12 +167,21 @@ private bool IsNPCSpawningOre(MineralNode node) return node.Type == MaterialType.FluxOre; } + // Timer to buffer excessive decrease messages on ore mining + private TimeSpan _bufferTime = TimeSpan.FromSeconds(1); + private DateTime _lastDecrease = DateTime.UtcNow; private void OnNodeDecrease(MineralNode node) { - if (IsNPCSpawningOre(node)) + var now = DateTime.UtcNow; + var diff = now - _lastDecrease; + if (diff > _bufferTime) { - var msg = new OreNpcSpawnMessage(node, Configuration.ZoneId, OreNodeState.Updated); - _eventChannel.PublishMessage(msg); + if (IsNPCSpawningOre(node)) + { + var msg = new OreNpcSpawnMessage(node, Configuration.ZoneId, OreNodeState.Updated); + _eventChannel.PublishMessage(msg); + } + _lastDecrease = now; } } From cfd5dd7864d4cb34bea981fe9ad10b9e6c7dd20d Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 13 May 2020 18:53:18 -0400 Subject: [PATCH 170/457] refactor OreNPCSpawner class --- .../EventProcessors/OreNPCSpawner.cs | 119 ++++++++++++------ 1 file changed, 81 insertions(+), 38 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index 0a5d1812f..948035de0 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -15,12 +15,13 @@ namespace Perpetuum.Services.EventServices.EventProcessors { /// - /// EventListener for each zone, receives messages for mineralnode mined - /// TODO need to track foreach node: is there a current npc, is there a new npc to be spawned + /// EventListener for each zone, receives messages for mineralnode mined and spawns npc presence based on the INpcReinforcementsRepository configurations /// public class OreNpcSpawner : EventProcessor { private const int SPAWN_DIST_FROM_FIELD = 50; + private readonly TimeSpan ORE_SPAWN_LIFETIME = TimeSpan.FromHours(3); + private readonly TimeSpan SPAWN_DELAY = TimeSpan.FromSeconds(10); private readonly IZone _zone; private readonly IDictionary spawnedPresences = new Dictionary(); @@ -57,7 +58,7 @@ private void RemoveEntry(MineralNode node) } } - private Position FindSpawnLocation(Position start, double range) + private Position TryFindSpawnLocation(Position start, double range) { var p = new Position(); List result = null; @@ -88,6 +89,64 @@ private double ComputeFieldPercentConsumed(MineralNode node) return percent; } + private void CheckMineralNodeReinforcements(OreNpcSpawnMessage msg) + { + var node = msg.GetMineralNode(); + if (!reinforcementsByMineralNode.ContainsKey(node)) + { + var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type); + reinforcementsByMineralNode.Add(node, oreSpawn); + } + } + + private bool IsNodeDead(OreNpcSpawnMessage msg) + { + if (msg.GetOreNodeState() == OreNodeState.Removed) + { + RemoveEntry(msg.GetMineralNode()); + return true; + } + return false; + } + + private bool AlreadyHasActivePresence(OreNpcSpawnMessage msg) + { + return spawnedPresences.ContainsKey(msg.GetMineralNode()); + } + + private Position FindSpawnPosition(OreNpcSpawnMessage msg) + { + var node = msg.GetMineralNode(); + var fieldCenter = node.Area.Center.ToPosition(); + return TryFindSpawnLocation(fieldCenter, SPAWN_DIST_FROM_FIELD); + } + + private INpcReinforcementWave GetNextWave(OreNpcSpawnMessage msg) + { + var node = msg.GetMineralNode(); + var percent = ComputeFieldPercentConsumed(node); + return reinforcementsByMineralNode[node].GetNextPresence(percent); + } + + private Position GetOreFieldCenter(OreNpcSpawnMessage msg) + { + return msg.GetMineralNode().Area.Center.ToPosition(); + } + + private 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)); + } + + private void DoSpawning(INpcReinforcementWave wave, Position homePosition, Position spawnPosition, MineralNode node) + { + var pres = _zone.AddDynamicPresenceToPosition(wave.Presence, homePosition, spawnPosition, ORE_SPAWN_LIFETIME); + wave.Spawned = true; + pres.PresenceExpired += OnPresenceExpired; + spawnedPresences.Add(node, pres); + } + private bool _spawning = false; public override void OnNext(EventMessage value) @@ -95,51 +154,35 @@ public override void OnNext(EventMessage value) if (value is OreNpcSpawnMessage msg && _zone.Id == msg.GetZoneID()) { if (_spawning) - { return; - } - var node = msg.GetMineralNode(); - if (!reinforcementsByMineralNode.ContainsKey(node)) - { - var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type); - reinforcementsByMineralNode.Add(node, oreSpawn); - } - Logger.Info(reinforcementsByMineralNode[node].ToString()); - if (spawnedPresences.ContainsKey(node)) - { - if (msg.GetOreNodeState() == OreNodeState.Removed) - { - //Node has been removed from zone - remove from our cache - RemoveEntry(node); - } - //There is an active presence + + CheckMineralNodeReinforcements(msg); + + if (IsNodeDead(msg)) return; - } - var fieldCenter = node.Area.Center.ToPosition(); - var spawnPos = FindSpawnLocation(fieldCenter, SPAWN_DIST_FROM_FIELD); + + if (AlreadyHasActivePresence(msg)) + return; + + var spawnPos = FindSpawnPosition(msg); if (spawnPos == Position.Empty) - { return; // Failed to find valid spawn location, try again on next cycle - } - var percent = ComputeFieldPercentConsumed(node); - var orePresence = reinforcementsByMineralNode[node].GetNextPresence(percent); - if (orePresence == null) - { + + var wave = GetNextWave(msg); + if (wave == null) return; // Presence already spawned once, or not found - } - var delay = TimeSpan.FromSeconds(10); - _zone.CreateBeam(BeamType.npc_egg_beam, b => b.WithPosition(fieldCenter).WithDuration(delay)); - _zone.CreateBeam(BeamType.teleport_storm, b => b.WithPosition(fieldCenter).WithDuration(delay)); + + var node = msg.GetMineralNode(); + var fieldCenter = GetOreFieldCenter(msg); + + DoBeams(fieldCenter); _spawning = true; - Task.Delay(delay).ContinueWith(t => + Task.Delay(SPAWN_DELAY).ContinueWith(t => { try { - var pres = _zone.AddDynamicPresenceToPosition(orePresence.Presence, fieldCenter, spawnPos, TimeSpan.FromSeconds(180)); // TODO timeout to 3 hr - orePresence.Spawned = true; - pres.PresenceExpired += OnPresenceExpired; - spawnedPresences.Add(node, pres); + DoSpawning(wave, fieldCenter, spawnPos, node); } catch (Exception ex) { From 66d9a67471a0c07a4ba7ac6672acce9e9f3b3aa5 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 13 May 2020 19:46:05 -0400 Subject: [PATCH 171/457] lint --- .../Zones/NpcSystem/Reinforcements/NpcReinforcements.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs index 81b9c8377..a3298daf0 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs @@ -10,7 +10,6 @@ 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(); From c2256060c88a1f1634ffae7c842ea56e70f32323 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 13 May 2020 21:55:27 -0400 Subject: [PATCH 172/457] hitsize bounding --- src/Perpetuum/Zones/LineOfSight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Zones/LineOfSight.cs b/src/Perpetuum/Zones/LineOfSight.cs index 0bff2ae7e..57c643fa0 100644 --- a/src/Perpetuum/Zones/LineOfSight.cs +++ b/src/Perpetuum/Zones/LineOfSight.cs @@ -55,14 +55,14 @@ public static LOSResult IsInLineOfSight(this IZone zone, Unit sourceUnit, Unit t if (targetUnit.HitSize > 0) { - var hitsize = targetUnit.HitSize * 0.6; + 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; From ed54c31bbf0f7fb17c20dfc365063d3ac5887d3f Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 13 May 2020 23:33:45 -0400 Subject: [PATCH 173/457] normalflock spawns incrementally, which we dont want --- src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs index 4a451312e..faa641d39 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs @@ -5,7 +5,7 @@ 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 : NormalFlock + public class RemoteSpawningFlock : Flock { public RemoteSpawningFlock(IFlockConfiguration configuration, Presence presence) : base(configuration, presence) { From 771fec16efea71428dfcb227903827933ed16cd8 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 15 May 2020 01:17:26 -0400 Subject: [PATCH 174/457] marakai's most excellent refactors + more useful timer class --- src/Perpetuum/Perpetuum.csproj | 1 + .../EventServices/EventListenerService.cs | 4 +- .../EventMessages/OreNpcSpawnMessage.cs | 27 ++------- .../EventProcessors/OreNPCSpawner.cs | 36 +++++------- src/Perpetuum/Timers/TimeKeeper.cs | 55 +++++++++++++++++++ src/Perpetuum/Timers/TimeTracker.cs | 8 +-- .../Reinforcements/NpcReinforcementWave.cs | 8 +-- .../Reinforcements/NpcReinforcements.cs | 14 ++--- .../Reinforcements/ReinforcementType.cs | 2 +- .../Materials/Minerals/MineralLayer.cs | 10 ++-- 10 files changed, 98 insertions(+), 67 deletions(-) create mode 100644 src/Perpetuum/Timers/TimeKeeper.cs diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d256af301..053742f45 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -566,6 +566,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventListenerService.cs b/src/Perpetuum/Services/EventServices/EventListenerService.cs index 16eb61f8c..f894af5bd 100644 --- a/src/Perpetuum/Services/EventServices/EventListenerService.cs +++ b/src/Perpetuum/Services/EventServices/EventListenerService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.EventServices.EventProcessors; +using Perpetuum.Timers; namespace Perpetuum.Services.EventServices { @@ -49,7 +50,8 @@ public void AttachListener(EventProcessor observer) public override void Update(TimeSpan time) { - while (!_queue.IsEmpty) + var timer = new TimeKeeper(TimeSpan.FromSeconds(1)); + while (!_queue.IsEmpty && !timer.Expired) { if (_queue.TryDequeue(out var message)) { diff --git a/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs index 8c54eff88..603c85138 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs @@ -10,30 +10,15 @@ public enum OreNodeState public class OreNpcSpawnMessage : EventMessage { - private readonly int _zoneId; - private readonly MineralNode _node; - private readonly OreNodeState _state; + public OreNodeState NodeState { get; } + public MineralNode Node { get; } + public int ZoneId { get; } public OreNpcSpawnMessage(MineralNode node, int zoneID, OreNodeState state) { - _zoneId = zoneID; - _node = node; - _state = state; - } - - public OreNodeState GetOreNodeState() - { - return _state; - } - - public int GetZoneID() - { - return _zoneId; - } - - public MineralNode GetMineralNode() - { - return _node; + ZoneId = zoneID; + Node = node; + NodeState = state; } } } diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index 948035de0..4e263cffc 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -60,38 +60,32 @@ private void RemoveEntry(MineralNode node) private Position TryFindSpawnLocation(Position start, double range) { - var p = new Position(); - List result = null; for (int i = 0; i < 10; i++) { var random = FastRandom.NextDouble(0.0, 1.0); var pos = start.OffsetInDirection(random, range); var posFinder = new ClosestWalkablePositionFinder(_zone, pos); - posFinder.Find(out p); - result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), 100); + posFinder.Find(out Position p); + var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), 100); if (result != null) { - break; + return p; } } - if (result == null) - { - return Position.Empty; - } - return p; + return Position.Empty; } private double ComputeFieldPercentConsumed(MineralNode node) { var current = Convert.ToInt32(node.GetTotalAmount()); - var total = _mineralConfigs.Where(c => c.Type == node.Type).First().TotalAmountPerNode; + var total = _mineralConfigs.Single(c => c.Type == node.Type).TotalAmountPerNode; var percent = 1.0 - (current / (double)total).Clamp(); return percent; } private void CheckMineralNodeReinforcements(OreNpcSpawnMessage msg) { - var node = msg.GetMineralNode(); + var node = msg.Node; if (!reinforcementsByMineralNode.ContainsKey(node)) { var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type); @@ -101,9 +95,9 @@ private void CheckMineralNodeReinforcements(OreNpcSpawnMessage msg) private bool IsNodeDead(OreNpcSpawnMessage msg) { - if (msg.GetOreNodeState() == OreNodeState.Removed) + if (msg.NodeState == OreNodeState.Removed) { - RemoveEntry(msg.GetMineralNode()); + RemoveEntry(msg.Node); return true; } return false; @@ -111,26 +105,25 @@ private bool IsNodeDead(OreNpcSpawnMessage msg) private bool AlreadyHasActivePresence(OreNpcSpawnMessage msg) { - return spawnedPresences.ContainsKey(msg.GetMineralNode()); + return spawnedPresences.ContainsKey(msg.Node); } private Position FindSpawnPosition(OreNpcSpawnMessage msg) { - var node = msg.GetMineralNode(); - var fieldCenter = node.Area.Center.ToPosition(); + var fieldCenter = msg.Node.Area.Center.ToPosition(); return TryFindSpawnLocation(fieldCenter, SPAWN_DIST_FROM_FIELD); } private INpcReinforcementWave GetNextWave(OreNpcSpawnMessage msg) { - var node = msg.GetMineralNode(); + var node = msg.Node; var percent = ComputeFieldPercentConsumed(node); return reinforcementsByMineralNode[node].GetNextPresence(percent); } private Position GetOreFieldCenter(OreNpcSpawnMessage msg) { - return msg.GetMineralNode().Area.Center.ToPosition(); + return msg.Node.Area.Center.ToPosition(); } private void DoBeams(Position beamLocation) @@ -151,7 +144,7 @@ private void DoSpawning(INpcReinforcementWave wave, Position homePosition, Posit public override void OnNext(EventMessage value) { - if (value is OreNpcSpawnMessage msg && _zone.Id == msg.GetZoneID()) + if (value is OreNpcSpawnMessage msg && _zone.Id == msg.ZoneId) { if (_spawning) return; @@ -172,7 +165,6 @@ public override void OnNext(EventMessage value) if (wave == null) return; // Presence already spawned once, or not found - var node = msg.GetMineralNode(); var fieldCenter = GetOreFieldCenter(msg); DoBeams(fieldCenter); @@ -182,7 +174,7 @@ public override void OnNext(EventMessage value) { try { - DoSpawning(wave, fieldCenter, spawnPos, node); + DoSpawning(wave, fieldCenter, spawnPos, msg.Node); } catch (Exception ex) { 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..7720d3685 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,7 @@ public TimeSpan Remaining get { return Duration - Elapsed; } } - public void Reset() + public virtual void Reset() { Elapsed = TimeSpan.Zero; } diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs index 75f6b0c98..a5c92140a 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs @@ -1,11 +1,9 @@ -using Perpetuum.Zones.Terrains.Materials; - -namespace Perpetuum.Zones.NpcSystem.Reinforcements +namespace Perpetuum.Zones.NpcSystem.Reinforcements { public class NpcReinforcementWave : INpcReinforcementWave { - public int Presence { get; private set; } - public double Threshold { get; private set; } + public int Presence { get; } + public double Threshold { get; } public bool Spawned { get; set; } public NpcReinforcementWave(int presenceID, double threshold) { diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs index a3298daf0..7485d29d6 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs @@ -1,5 +1,4 @@ -using Perpetuum.Zones.Terrains.Materials; -using System; +using System; using System.Linq; using System.Text; @@ -15,17 +14,18 @@ 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 percentageOfFieldConsumed) { for (var i = 0; i < _presences.Length; i++) { if (_presences[i].Threshold > percentageOfFieldConsumed) { - if (_presences[i].Spawned) - { - return null; - } - return _presences[i]; + return _presences[i].Spawned ? null : _presences[i]; } } return null; diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs index 346bc229a..e813739de 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/ReinforcementType.cs @@ -5,4 +5,4 @@ public enum ReinforcementType Minerals = 1, Boss = 2 } -} \ No newline at end of file +} diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs index 7f2798dd5..613518a78 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs @@ -5,6 +5,7 @@ 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; @@ -168,20 +169,17 @@ private bool IsNPCSpawningOre(MineralNode node) } // Timer to buffer excessive decrease messages on ore mining - private TimeSpan _bufferTime = TimeSpan.FromSeconds(1); - private DateTime _lastDecrease = DateTime.UtcNow; + private readonly TimeKeeper _time = new TimeKeeper(TimeSpan.FromSeconds(1)); private void OnNodeDecrease(MineralNode node) { - var now = DateTime.UtcNow; - var diff = now - _lastDecrease; - if (diff > _bufferTime) + if (_time.Expired) { if (IsNPCSpawningOre(node)) { var msg = new OreNpcSpawnMessage(node, Configuration.ZoneId, OreNodeState.Updated); _eventChannel.PublishMessage(msg); } - _lastDecrease = now; + _time.Reset(); } } From db4ef88ca8378f1bfd88e616f06989be7edef53a Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 16 May 2020 02:20:57 -0400 Subject: [PATCH 175/457] add zone to model and filter --- .../EventServices/EventProcessors/OreNPCSpawner.cs | 2 +- .../Reinforcements/INpcReinforcementsRepository.cs | 4 ++-- .../Reinforcements/NpcReinforcementsRepository.cs | 9 ++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index 4e263cffc..b05e637b8 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -88,7 +88,7 @@ private void CheckMineralNodeReinforcements(OreNpcSpawnMessage msg) var node = msg.Node; if (!reinforcementsByMineralNode.ContainsKey(node)) { - var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type); + var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type, msg.ZoneId); reinforcementsByMineralNode.Add(node, oreSpawn); } } diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs index c5b01a5ea..91c3b64dd 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementsRepository.cs @@ -7,7 +7,7 @@ namespace Perpetuum.Zones.NpcSystem.Reinforcements /// public interface INpcReinforcementsRepository { - INpcReinforcements CreateOreNPCSpawn(MaterialType materialType); - INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo); + INpcReinforcements CreateOreNPCSpawn(MaterialType materialType, int zoneId); + INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo, int zoneId); } } diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs index 07c46c653..06c9e9cec 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs @@ -7,11 +7,14 @@ namespace Perpetuum.Zones.NpcSystem.Reinforcements { public class NpcReinforcementsRepository : INpcReinforcementsRepository { - public INpcReinforcements CreateOreNPCSpawn(MaterialType materialType) + 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("SELECT threshold, presenceId from npcreinforcements WHERE targetId=@target AND reinforcementType=@type") + var records = Db.Query().CommandText(queryStr) .SetParameter("@target", materialType) .SetParameter("@type", ReinforcementType.Minerals) + .SetParameter("@zone", zoneId) .Execute() .Select(CreateFromRecord).ToArray(); return new NpcReinforcements(records); @@ -25,7 +28,7 @@ private static INpcReinforcementWave CreateFromRecord(IDataRecord record) return pair; } - public INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo) + public INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo, int zoneId) { throw new System.NotImplementedException(); //TODO new feature =) } From 092e2b73193ae6e0579b8b0d1b95146a5082f5ef Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 16 May 2020 20:47:09 -0400 Subject: [PATCH 176/457] Allow multiple waves to spawn and be active per node --- .../EventProcessors/OreNPCSpawner.cs | 42 +++++++++---------- .../Reinforcements/INpcReinforcementWave.cs | 21 ++++++++-- .../Reinforcements/INpcReinforcements.cs | 24 ++++++++++- .../Reinforcements/NpcReinforcementWave.cs | 24 ++++++++--- .../Reinforcements/NpcReinforcements.cs | 18 +++++++- 5 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index b05e637b8..fb76b5970 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -8,7 +8,6 @@ using Perpetuum.Zones.Terrains.Materials.Minerals; using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -19,12 +18,11 @@ namespace Perpetuum.Services.EventServices.EventProcessors /// public class OreNpcSpawner : EventProcessor { - private const int SPAWN_DIST_FROM_FIELD = 50; + private const int SPAWN_DIST_FROM_FIELD = 60; private readonly TimeSpan ORE_SPAWN_LIFETIME = TimeSpan.FromHours(3); private readonly TimeSpan SPAWN_DELAY = TimeSpan.FromSeconds(10); private readonly IZone _zone; - private readonly IDictionary spawnedPresences = new Dictionary(); private readonly IDictionary reinforcementsByMineralNode = new Dictionary(); private readonly INpcReinforcementsRepository _npcReinforcementsRepo; private readonly IEnumerable _mineralConfigs; @@ -38,26 +36,33 @@ public OreNpcSpawner(IZone zone, INpcReinforcementsRepository reinforcementsRepo private void OnPresenceExpired(Presence presence) { - var matchedEntries = spawnedPresences.Where(p => p.Value.Equals(presence)).ToList(); + var matchedEntries = reinforcementsByMineralNode.Where(p => p.Value.HasActivePresence(presence)).ToList(); foreach (var pair in matchedEntries) { - pair.Value.PresenceExpired -= OnPresenceExpired; - spawnedPresences.Remove(pair.Key); + var wave = pair.Value.GetActiveWaveOfPresence(presence); + ExpireWave(wave); } } - private void RemoveEntry(MineralNode node) + private void CleanupMineralNodeReinforcements(MineralNode node) { - if (spawnedPresences.ContainsKey(node)) + if (reinforcementsByMineralNode.ContainsKey(node)) { - if (spawnedPresences[node] != null) + var activeWaves = reinforcementsByMineralNode[node].GetAllActiveWaves(); + foreach (var wave in activeWaves) { - spawnedPresences[node].PresenceExpired -= OnPresenceExpired; + ExpireWave(wave); } - spawnedPresences.Remove(node); + reinforcementsByMineralNode.Remove(node); } } + private void ExpireWave(INpcReinforcementWave wave) + { + wave.ActivePresence.PresenceExpired -= OnPresenceExpired; + wave.DeactivatePresence(); + } + private Position TryFindSpawnLocation(Position start, double range) { for (int i = 0; i < 10; i++) @@ -97,17 +102,12 @@ private bool IsNodeDead(OreNpcSpawnMessage msg) { if (msg.NodeState == OreNodeState.Removed) { - RemoveEntry(msg.Node); + CleanupMineralNodeReinforcements(msg.Node); return true; } return false; } - private bool AlreadyHasActivePresence(OreNpcSpawnMessage msg) - { - return spawnedPresences.ContainsKey(msg.Node); - } - private Position FindSpawnPosition(OreNpcSpawnMessage msg) { var fieldCenter = msg.Node.Area.Center.ToPosition(); @@ -134,10 +134,9 @@ private void DoBeams(Position beamLocation) private void DoSpawning(INpcReinforcementWave wave, Position homePosition, Position spawnPosition, MineralNode node) { - var pres = _zone.AddDynamicPresenceToPosition(wave.Presence, homePosition, spawnPosition, ORE_SPAWN_LIFETIME); - wave.Spawned = true; + var pres = _zone.AddDynamicPresenceToPosition(wave.PresenceId, homePosition, spawnPosition, ORE_SPAWN_LIFETIME); pres.PresenceExpired += OnPresenceExpired; - spawnedPresences.Add(node, pres); + wave.ActivePresence = pres; } private bool _spawning = false; @@ -154,9 +153,6 @@ public override void OnNext(EventMessage value) if (IsNodeDead(msg)) return; - if (AlreadyHasActivePresence(msg)) - return; - var spawnPos = FindSpawnPosition(msg); if (spawnPos == Position.Empty) return; // Failed to find valid spawn location, try again on next cycle diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs index 4ec3d111a..e3a6d6cee 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs @@ -1,12 +1,27 @@ -namespace Perpetuum.Zones.NpcSystem.Reinforcements +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements { /// /// Represents a single npc presence and its spawned state /// public interface INpcReinforcementWave { - int Presence { get; } + int PresenceId { get; } + DynamicPresence ActivePresence { get; set; } double Threshold { get; } - bool Spawned { get; set; } + bool Spawned { get; } + + /// + /// 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 index ff5ce377a..1fabedd30 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs @@ -1,4 +1,6 @@ -namespace Perpetuum.Zones.NpcSystem.Reinforcements +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements { /// /// Ore NPC Spawn - a list of presences that spawn at particular levels of field depletion @@ -11,5 +13,25 @@ public interface INpcReinforcements /// double of range [0.-1.] representing a percentage of ore field mined /// IOreNpcData INpcReinforcementWave GetNextPresence(double percentageOfFieldConsumed); + + /// + /// 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/NpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs index a5c92140a..7472cd2dd 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs @@ -1,19 +1,33 @@ -namespace Perpetuum.Zones.NpcSystem.Reinforcements +using Perpetuum.Zones.NpcSystem.Presences; + +namespace Perpetuum.Zones.NpcSystem.Reinforcements { public class NpcReinforcementWave : INpcReinforcementWave { - public int Presence { get; } + public int PresenceId { get; } public double Threshold { get; } - public bool Spawned { get; set; } + public bool Spawned => ActivePresence != null; + public DynamicPresence ActivePresence { get; set; } + public NpcReinforcementWave(int presenceID, double threshold) { - Presence = presenceID; + PresenceId = presenceID; Threshold = threshold; } public override string ToString() { - return $"{Threshold}:{Presence} Spawned? {Spawned}"; + return $"{Threshold}:{PresenceId} Spawned? {Spawned}"; + } + + 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 index 7485d29d6..f50f2ef0e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs @@ -1,4 +1,5 @@ -using System; +using Perpetuum.Zones.NpcSystem.Presences; +using System; using System.Linq; using System.Text; @@ -31,6 +32,21 @@ public INpcReinforcementWave GetNextPresence(double percentageOfFieldConsumed) 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(); From 57f70a66383ae274a163807319c0894f866a1125 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sun, 17 May 2020 02:06:40 -0400 Subject: [PATCH 177/457] update floodfill area test const, refactors --- .../RelicManagers/OutpostRelicManager.cs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs index 66499df50..2fd17e9f8 100644 --- a/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs +++ b/src/Perpetuum/Services/Relics/RelicManagers/OutpostRelicManager.cs @@ -16,17 +16,17 @@ public class OutpostRelicManager : AbstractRelicManager private readonly TimeSpan RESPAWN_RANDOM_WINDOW = TimeSpan.FromHours(1); private readonly TimeSpan _respawnRate = TimeSpan.FromHours(4); - private Outpost _outpost; - private Random _random; - private RelicInfo _sapRelicInfo; + 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 readonly int SPAWN_MINIMUM_OUTPOST_DISTANCE = 90; - private readonly int SPAWN_MAXIMUM_OUTPOST_DISTANCE = 350; - private readonly int SPAWN_AREA_REQUIRED_SIZE = 200; + 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 @@ -77,30 +77,20 @@ protected override RelicInfo GetNextRelicType() protected override Point FindRelicPosition(RelicInfo info) { - Position p = new Position(); - Position invalidPosition = new Position(0, 0); - - List result = null; 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 p); - result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); + posFinder.Find(out Position p); + var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); if(result != null) { - break; + return p; } } - - if (result == null) - { - p = invalidPosition; - } - - return p; + return Point.Empty; } protected override void RefreshBeam(IRelic relic) From b552127aca7d5f5bf98557a7497e195c9fc9348c Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sun, 17 May 2020 02:38:30 -0400 Subject: [PATCH 178/457] shorter lifespan of outpost relics --- src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs | 2 +- src/Perpetuum/Services/Relics/Relics/SAPRelic.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs b/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs index f7a9632f9..942582449 100644 --- a/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs +++ b/src/Perpetuum/Services/Relics/Relics/AbstractRelic.cs @@ -20,8 +20,8 @@ public class AbstractRelic : Unit, IRelic protected bool _alive; protected const double ACTIVATION_RANGE = 3; //30m - protected readonly TimeSpan MAXLIFESPAN = TimeSpan.FromDays(3); 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 diff --git a/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs index 3f66c8aed..9e83a6d1b 100644 --- a/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs +++ b/src/Perpetuum/Services/Relics/Relics/SAPRelic.cs @@ -3,12 +3,14 @@ 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) From c2c42a79dc7821ce86166ed4406652c3674f27cb Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 20 May 2020 22:41:46 -0400 Subject: [PATCH 179/457] spawn in range change request --- .../Services/EventServices/EventProcessors/OreNPCSpawner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index fb76b5970..df2ddd0e8 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -18,7 +18,7 @@ namespace Perpetuum.Services.EventServices.EventProcessors /// public class OreNpcSpawner : EventProcessor { - private const int SPAWN_DIST_FROM_FIELD = 60; + private const int SPAWN_DIST_FROM_FIELD = 100; private readonly TimeSpan ORE_SPAWN_LIFETIME = TimeSpan.FromHours(3); private readonly TimeSpan SPAWN_DELAY = TimeSpan.FromSeconds(10); From 734b748bde9a9b54a595dcdd3c3664e47e1f1997 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 22 May 2020 18:25:47 -0400 Subject: [PATCH 180/457] AOE srf-area to radius of explosion curve --- src/Perpetuum/Units/Unit.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index d5e6fe181..0160a171e 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -482,9 +482,21 @@ protected virtual void DoExplosion() 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 radius = SignatureRadius * 0.5; //Note: reduced for increased bot srf-areas + var radius = SigRadiusToExplosionRadius(SignatureRadius); var damageBuilder = DamageInfo.Builder.WithAttacker(this) .WithOptimalRange(1) .WithFalloff(radius) @@ -500,7 +512,7 @@ private IDamageBuilder GetExplosionDamageBuilder() if (coreMax.IsZero()) coreMax = 1.0; - var damage = (Math.Sin( Core.Ratio(coreMax) * Math.PI) + 1)*(armorMaxValue*0.1); + var damage = (Math.Sin(Core.Ratio(coreMax) * Math.PI) + 1) * (armorMaxValue * 0.1); damageBuilder.WithAllDamageTypes(damage); return damageBuilder; } From fe787ffcb1737bd0e0a99dcb1787ea20f6f3b9b4 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 22 May 2020 18:26:08 -0400 Subject: [PATCH 181/457] Fixes #140 --- src/Perpetuum/Zones/LineOfSight.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/LineOfSight.cs b/src/Perpetuum/Zones/LineOfSight.cs index 57c643fa0..a09dcbe01 100644 --- a/src/Perpetuum/Zones/LineOfSight.cs +++ b/src/Perpetuum/Zones/LineOfSight.cs @@ -98,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 == 0.0) + { + 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; From f53e5a1a57851483140ba0c8dbddbfbded36fa57 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sun, 24 May 2020 12:43:11 -0400 Subject: [PATCH 182/457] Spawned state needs to be tracked to avoid respawning same wave --- .../EventServices/EventProcessors/OreNPCSpawner.cs | 2 +- .../NpcSystem/Reinforcements/INpcReinforcementWave.cs | 8 +++++++- .../NpcSystem/Reinforcements/NpcReinforcementWave.cs | 10 ++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index df2ddd0e8..f9c96ead4 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -136,7 +136,7 @@ private void DoSpawning(INpcReinforcementWave wave, Position homePosition, Posit { var pres = _zone.AddDynamicPresenceToPosition(wave.PresenceId, homePosition, spawnPosition, ORE_SPAWN_LIFETIME); pres.PresenceExpired += OnPresenceExpired; - wave.ActivePresence = pres; + wave.SetActivePresence(pres); } private bool _spawning = false; diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs index e3a6d6cee..1240a87eb 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcementWave.cs @@ -8,10 +8,16 @@ namespace Perpetuum.Zones.NpcSystem.Reinforcements public interface INpcReinforcementWave { int PresenceId { get; } - DynamicPresence ActivePresence { get; set; } + 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 /// diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs index 7472cd2dd..52d53557b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementWave.cs @@ -6,8 +6,8 @@ public class NpcReinforcementWave : INpcReinforcementWave { public int PresenceId { get; } public double Threshold { get; } - public bool Spawned => ActivePresence != null; - public DynamicPresence ActivePresence { get; set; } + public bool Spawned { get; private set; } + public DynamicPresence ActivePresence { get; private set; } public NpcReinforcementWave(int presenceID, double threshold) { @@ -20,6 +20,12 @@ 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); From 4d455bcc84d408cdee888b7901cad9d9d415109b Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 26 May 2020 22:57:18 -0400 Subject: [PATCH 183/457] use same parameters from relics --- .../Services/EventServices/EventProcessors/OreNPCSpawner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index f9c96ead4..72a1db470 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -19,6 +19,7 @@ namespace Perpetuum.Services.EventServices.EventProcessors public class OreNpcSpawner : EventProcessor { private const int SPAWN_DIST_FROM_FIELD = 100; + private const int SPAWN_AREA_REQUIRED_SIZE = 3000; private readonly TimeSpan ORE_SPAWN_LIFETIME = TimeSpan.FromHours(3); private readonly TimeSpan SPAWN_DELAY = TimeSpan.FromSeconds(10); @@ -71,7 +72,7 @@ private Position TryFindSpawnLocation(Position start, double range) var pos = start.OffsetInDirection(random, range); var posFinder = new ClosestWalkablePositionFinder(_zone, pos); posFinder.Find(out Position p); - var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), 100); + var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); if (result != null) { return p; From 7f1e2eef17f14121f61ab89d56f0a1b6aaee66d8 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Fri, 29 May 2020 15:44:12 -0400 Subject: [PATCH 184/457] approxEqual --- src/Perpetuum/ValueTypeExtensions.cs | 12 ++++++++++++ src/Perpetuum/Zones/LineOfSight.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/ValueTypeExtensions.cs b/src/Perpetuum/ValueTypeExtensions.cs index 3c22c694a..94ff6a281 100644 --- a/src/Perpetuum/ValueTypeExtensions.cs +++ b/src/Perpetuum/ValueTypeExtensions.cs @@ -85,6 +85,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/Zones/LineOfSight.cs b/src/Perpetuum/Zones/LineOfSight.cs index a09dcbe01..d0131bc2d 100644 --- a/src/Perpetuum/Zones/LineOfSight.cs +++ b/src/Perpetuum/Zones/LineOfSight.cs @@ -100,7 +100,7 @@ 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) { - if (distance == 0.0) + if (distance.IsApproximatelyEqual(0.0f)) { var blockingInfo = zone.Terrain.Blocks.GetValue(origin); var losResult = new LOSResult From 4dc12b293f12dd93718c0b38ac4503127f2d8a36 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Tue, 2 Jun 2020 17:36:00 -0400 Subject: [PATCH 185/457] falloff for missiles modified on the module, set by the ammo --- src/Perpetuum.ExportedTypes/AggregateField.cs | 6 ++- src/Perpetuum/Items/Ammos/Ammo.cs | 8 ++++ src/Perpetuum/Modules/ActiveModule.cs | 40 ++++++++++++------- .../Modules/Weapons/MissileWeaponModule.cs | 8 ++++ 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 9615ea4da..31ded26f3 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -332,10 +332,12 @@ public enum AggregateField missile_cycle_time_modifier = 253, missile_miss = 341, missile_miss_modifier = 342, - missile_range_modifier = 254, + missile_falloff_modifier = 658, + missile_range_modifier = 254, mobile_teleport_cooldown = 579, mobile_teleport_range = 577, - module_missile_range_modifier = 586, + module_missile_falloff_modifier = 659, + module_missile_range_modifier = 586, optimal_range = 255, optimal_range_modifier = 256, pbs_turret_core_per_sec = 652, diff --git a/src/Perpetuum/Items/Ammos/Ammo.cs b/src/Perpetuum/Items/Ammos/Ammo.cs index d3089468c..c2c9445e6 100644 --- a/src/Perpetuum/Items/Ammos/Ammo.cs +++ b/src/Perpetuum/Items/Ammos/Ammo.cs @@ -20,6 +20,14 @@ public ItemPropertyModifier OptimalRangePropertyModifier } } + public ItemPropertyModifier FalloffRangePropertyModifier + { + get + { + return GetPropertyModifier(AggregateField.falloff); + } + } + public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index b71928ebd..351b228ba 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -113,8 +113,7 @@ protected override double CalculateValue() var optimalRange = ItemPropertyModifier.Create(AggregateField.optimal_range); var ammo = _module.GetAmmo(); - var m = module as MissileWeaponModule; - if (m != null) + if (module is MissileWeaponModule m) { if (ammo != null) { @@ -138,18 +137,31 @@ protected override double CalculateValue() public class FalloffProperty : ModuleProperty { - public FalloffProperty(Module module) : base(module, AggregateField.falloff) + public FalloffProperty(ActiveModule module) : base(module, AggregateField.falloff) { } protected override double CalculateValue() { - var p = module.GetPropertyModifier(AggregateField.falloff); - - var ammo = ((ActiveModule) module).GetAmmo(); - ammo?.ModifyFalloff(ref p); - - return p.Value; + 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; } } @@ -159,7 +171,7 @@ 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 ItemProperty falloff = ItemProperty.None; protected readonly ModuleProperty optimalRange; private readonly CategoryFlags _ammoCategoryFlags; @@ -176,8 +188,8 @@ 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; @@ -212,7 +224,7 @@ public double OptimalRange protected double Falloff { - get { return _falloff.Value; } + get { return falloff.Value; } } public Lock Lock @@ -463,7 +475,7 @@ protected override void OnUpdateProperty(AggregateField field) } case AggregateField.falloff: { - _falloff.Update(); + falloff.Update(); break; } } diff --git a/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs b/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs index a352c4edd..71c2a092d 100644 --- a/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs @@ -9,6 +9,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 +18,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 +44,11 @@ public override void UpdateProperty(AggregateField field) MissileRangeModifier.Update(); return; } + case AggregateField.module_missile_falloff_modifier: + { + MissileFalloffModifier.Update(); + return; + } } base.UpdateProperty(field); From d0f0705d2235f67b9aef383ce00762935dac93d7 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 11 Jun 2020 00:28:24 -0400 Subject: [PATCH 186/457] min distance check, handle zone edge spawns --- .../Services/EventServices/EventProcessors/OreNPCSpawner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs index 72a1db470..13e14130a 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs @@ -19,7 +19,9 @@ namespace Perpetuum.Services.EventServices.EventProcessors public class OreNpcSpawner : EventProcessor { private const int SPAWN_DIST_FROM_FIELD = 100; + private const int MIN_SPAWN_DIST_TOLERANCE = 30; private const int SPAWN_AREA_REQUIRED_SIZE = 3000; + private const int POSITION_FIND_ATTEMPTS = 10; private readonly TimeSpan ORE_SPAWN_LIFETIME = TimeSpan.FromHours(3); private readonly TimeSpan SPAWN_DELAY = TimeSpan.FromSeconds(10); @@ -66,14 +68,14 @@ private void ExpireWave(INpcReinforcementWave wave) private Position TryFindSpawnLocation(Position start, double range) { - for (int i = 0; i < 10; i++) + for (int i = 0; i < POSITION_FIND_ATTEMPTS; i++) { var random = FastRandom.NextDouble(0.0, 1.0); var pos = start.OffsetInDirection(random, range); var posFinder = new ClosestWalkablePositionFinder(_zone, pos); posFinder.Find(out Position p); var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); - if (result != null) + if (result != null && p.TotalDistance2D(start) > range - MIN_SPAWN_DIST_TOLERANCE) { return p; } From c0ba5a9180ae65366056b389d04d4da65d9cccec Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 11 Jun 2020 16:18:54 -0400 Subject: [PATCH 187/457] fixes #133 --- .../Missions/MissionInProgress.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index c2877a2a0..34c715a10 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1355,12 +1355,12 @@ public IList SpawnRewardItems(out MissionLocation locationUsed) //If item is tokens, split amoung participants if (myLocation.GetRaceSpecificCoinDefinition() == reward.ItemInfo.Definition) { - if (_participants.Count > 0 && myLocation.GetRaceSpecificCoinDefinition() == reward.ItemInfo.Definition) + if (GetParticipants().Count > 0 && myLocation.GetRaceSpecificCoinDefinition() == reward.ItemInfo.Definition) { - var tokenSplitQuantity = (int)Math.Ceiling((double)reward.ItemInfo.Quantity / (double)_participants.Count); + var tokenSplitQuantity = (int)Math.Ceiling((double)reward.ItemInfo.Quantity / (double)GetParticipants().Count); var splitTokens = new MissionReward(new ItemInfo(reward.ItemInfo.Definition, tokenSplitQuantity)); Item tokenItem = null; - foreach (var participant in _participants) + foreach (var participant in GetParticipants()) { var rewardItem = publicContainer.CreateAndAddItem(splitTokens.ItemInfo, false, item => { item.Owner = participant.Eid; }); @@ -1461,10 +1461,10 @@ public double GetDifficultyReward public double GetParticipantBonusModifier() { - return computeParticipantBonusMultiplier(this._participants.Count); + return ComputeParticipantBonusMultiplier(GetParticipants().Count); } - private double computeParticipantBonusMultiplier(int paricipantCount) + private double ComputeParticipantBonusMultiplier(int paricipantCount) { //Solo or squad of 1 and initial estimate if (paricipantCount < 2) @@ -1479,10 +1479,10 @@ private double computeParticipantBonusMultiplier(int paricipantCount) return participantModifier; } - /// - /// Both old and new tech handled - /// - public void PayOutMission(double rewardFraction, List participants, List onlineGangMembers, Dictionary successData) + /// + /// Both old and new tech handled + /// + public void PayOutMission(double rewardFraction, List participants, List onlineGangMembers, Dictionary successData) { var paymentData = new Dictionary(); From b0a9372451934aefd041bb91f82755b859092f08 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 11 Jun 2020 16:27:34 -0400 Subject: [PATCH 188/457] reduce number of calls --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index 34c715a10..eb617f31d 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1355,12 +1355,13 @@ public IList SpawnRewardItems(out MissionLocation locationUsed) //If item is tokens, split amoung participants if (myLocation.GetRaceSpecificCoinDefinition() == reward.ItemInfo.Definition) { - if (GetParticipants().Count > 0 && 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)GetParticipants().Count); + 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 GetParticipants()) + foreach (var participant in participants) { var rewardItem = publicContainer.CreateAndAddItem(splitTokens.ItemInfo, false, item => { item.Owner = participant.Eid; }); From 6a638f00fb5f6bd55e62f2eeeaed828ae5b0d240 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Mon, 15 Jun 2020 13:38:39 -0400 Subject: [PATCH 189/457] typo, refactor --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index eb617f31d..1cf49b968 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1465,17 +1465,17 @@ public double GetParticipantBonusModifier() return ComputeParticipantBonusMultiplier(GetParticipants().Count); } - private double ComputeParticipantBonusMultiplier(int paricipantCount) + private double ComputeParticipantBonusMultiplier(int participantCount) { //Solo or squad of 1 and initial estimate - if (paricipantCount < 2) + 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 - int participantCount = Math.Min(MaxmimalGangParticipants, Math.Max(1, paricipantCount)); + participantCount = participantCount.Clamp(1, MaxmimalGangParticipants); double participantModifier = 1 + participantCount * participantBonus; return participantModifier; } From 6a21c2aac84c321495dbcc86b9811cd12b173ff7 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Mon, 15 Jun 2020 13:44:28 -0400 Subject: [PATCH 190/457] lint --- .../Services/MissionEngine/Missions/MissionInProgress.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs index 1cf49b968..316b2806c 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -1477,7 +1477,7 @@ private double ComputeParticipantBonusMultiplier(int participantCount) double participantBonus = 0.05; //TODO expose parameter in DB participantCount = participantCount.Clamp(1, MaxmimalGangParticipants); double participantModifier = 1 + participantCount * participantBonus; - return participantModifier; + return participantModifier; } /// From 8c2c34ca7a6e88157e3779e3ec4f1af80a286d60 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 17 Jun 2020 23:15:34 -0400 Subject: [PATCH 191/457] dont flag on non-player emitted emitters --- src/Perpetuum/Modules/BlobEmissionModulatorModule.cs | 3 +++ src/Perpetuum/Players/Player.cs | 7 ++++++- src/Perpetuum/Zones/Blobs/BlobEmitters/BlobEmitterUnit.cs | 6 ++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs index e5db6c246..a79aaee59 100644 --- a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs +++ b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs @@ -2,6 +2,7 @@ using System.Linq; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -95,6 +96,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(); diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 6d47c4353..89236a924 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -721,7 +721,12 @@ public override void OnAggression(Unit victim) if (victim is ITaggable taggable) taggable.Tag(this,TimeSpan.Zero); - if ( victim is MobileTeleport || victim is BlobEmitterUnit || victim is IPBSObject || victim is WallHealer || victim is ProximityProbeBase) + if (victim is BlobEmitterUnit b && b.IsPlayerSpawned) + { + ApplyPvPEffect(); + return; + } + else if (victim is MobileTeleport || victim is IPBSObject || victim is WallHealer || victim is ProximityProbeBase) { ApplyPvPEffect(); return; 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 From 72406f11b1a8b04ff582651315578547bc268411 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 18 Jun 2020 13:30:57 -0400 Subject: [PATCH 192/457] refactor conditional statement --- src/Perpetuum/Players/Player.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 89236a924..b0527f910 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -712,6 +712,15 @@ public override ItemPropertyModifier GetPropertyModifier(AggregateField field) return modifier; } + private bool IsUnitPVPAggro(Unit unit) + { + return unit is MobileTeleport + || unit is IPBSObject + || unit is WallHealer + || unit is ProximityProbeBase + || (unit is BlobEmitterUnit b && b.IsPlayerSpawned); + } + public override void OnAggression(Unit victim) { base.OnAggression(victim); @@ -719,14 +728,9 @@ public override void OnAggression(Unit victim) AddInCombatWith(victim); if (victim is ITaggable taggable) - taggable.Tag(this,TimeSpan.Zero); + taggable.Tag(this, TimeSpan.Zero); - if (victim is BlobEmitterUnit b && b.IsPlayerSpawned) - { - ApplyPvPEffect(); - return; - } - else if (victim is MobileTeleport || victim is IPBSObject || victim is WallHealer || victim is ProximityProbeBase) + if (IsUnitPVPAggro(victim)) { ApplyPvPEffect(); return; From d296cabeef1507a7b6e961c347de950c9d17d4ef Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 24 Jun 2020 11:58:11 -0400 Subject: [PATCH 193/457] getter refactor --- src/Perpetuum/Items/Ammos/Ammo.cs | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/Perpetuum/Items/Ammos/Ammo.cs b/src/Perpetuum/Items/Ammos/Ammo.cs index c2c9445e6..71b186f19 100644 --- a/src/Perpetuum/Items/Ammos/Ammo.cs +++ b/src/Perpetuum/Items/Ammos/Ammo.cs @@ -7,26 +7,9 @@ namespace Perpetuum.Items.Ammos { public class Ammo : Item { - public double BulletTime - { - get { return ED.Options.BulletTime; } - } - - public ItemPropertyModifier OptimalRangePropertyModifier - { - get - { - return GetPropertyModifier(AggregateField.optimal_range); - } - } - - public ItemPropertyModifier FalloffRangePropertyModifier - { - get - { - return GetPropertyModifier(AggregateField.falloff); - } - } + public double BulletTime => ED.Options.BulletTime; + public ItemPropertyModifier OptimalRangePropertyModifier => GetPropertyModifier(AggregateField.optimal_range); + public ItemPropertyModifier FalloffRangePropertyModifier => GetPropertyModifier(AggregateField.falloff); public override void AcceptVisitor(IEntityVisitor visitor) { From 1ae28117393e34fe6d02fa1bf81a200d69451ec6 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 24 Jun 2020 12:11:36 -0400 Subject: [PATCH 194/457] addl refactor --- src/Perpetuum/Items/Ammos/Ammo.cs | 2 ++ src/Perpetuum/Modules/ActiveModule.cs | 20 ++++---------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Perpetuum/Items/Ammos/Ammo.cs b/src/Perpetuum/Items/Ammos/Ammo.cs index 71b186f19..796103ec4 100644 --- a/src/Perpetuum/Items/Ammos/Ammo.cs +++ b/src/Perpetuum/Items/Ammos/Ammo.cs @@ -8,7 +8,9 @@ namespace Perpetuum.Items.Ammos public class Ammo : Item { public double BulletTime => ED.Options.BulletTime; + public ItemPropertyModifier OptimalRangePropertyModifier => GetPropertyModifier(AggregateField.optimal_range); + public ItemPropertyModifier FalloffRangePropertyModifier => GetPropertyModifier(AggregateField.falloff); public override void AcceptVisitor(IEntityVisitor visitor) diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 351b228ba..beaf6435c 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -207,25 +207,13 @@ protected ActiveModule(bool ranged) : this(CategoryFlags.undefined,ranged) { } - public TimeSpan CycleTime - { - get { return TimeSpan.FromMilliseconds(cycleTime.Value); } - } + public TimeSpan CycleTime => TimeSpan.FromMilliseconds(cycleTime.Value); - public double CoreUsage - { - get { return coreUsage.Value; } - } + public double CoreUsage => coreUsage.Value; - public double OptimalRange - { - get { return optimalRange.Value; } - } + public double OptimalRange => optimalRange.Value; - protected double Falloff - { - get { return falloff.Value; } - } + protected double Falloff => falloff.Value; public Lock Lock { From 46e96276a9b5feda01686025803f3f0fd71d7096 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Wed, 22 Jul 2020 23:07:15 -0400 Subject: [PATCH 195/457] refactoring of zone equip/remove, shield ref update on requip actions --- .../Zone/Containers/EquipModule.cs | 86 +++---------------- .../Zone/Containers/RemoveModule.cs | 67 ++------------- .../Zone/Containers/ZoneChangeModule.cs | 53 +++++++++--- src/Perpetuum/Units/Unit.cs | 5 ++ .../Zones/DamageProcessors/DamageProcessor.cs | 9 +- 5 files changed, 74 insertions(+), 146 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs index 88b79c02e..8b548d2cd 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs @@ -1,84 +1,26 @@ -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); - //Store ratios before transaction - var hpRatio = player.ArmorPercentage; - var apRatio = player.CorePercentage; - - 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); - - //Apply original ratios after unequip-action - player.Armor = player.ArmorMax * hpRatio; - player.Core = player.CoreMax * apRatio; - - 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 moduleEid = request.Data.GetOrDefault(k.moduleEID); - var module = (Module)container.GetItemOrThrow(moduleEid).Unstack(1); - module.CheckEnablerExtensionsAndThrowIfFailed(player.Character); - component.EquipModuleOrThrow(module, slot); - - player.Initialize(character); - player.CheckEnergySystemAndThrowIfFailed(); - - player.Save(); - container.Save(); - - scope.Complete(); - } + 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 moduleEid = request.Data.GetOrDefault(k.moduleEID); + var module = (Module)container.GetItemOrThrow(moduleEid).Unstack(1); + module.CheckEnablerExtensionsAndThrowIfFailed(player.Character); + 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 de75fd496..6f391d9bf 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs @@ -1,68 +1,17 @@ -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); - //Store ratios before transaction - var hpRatio = player.ArmorPercentage; - var apRatio = player.CorePercentage; - - 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); - - //Apply original ratios after equip-action - player.Armor = player.ArmorMax * hpRatio; - player.Core = player.CoreMax * apRatio; - - 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); + 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 ed98edce9..8b5b3c103 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -1,15 +1,22 @@ using System.Collections.Generic; using System.Transactions; +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()) @@ -17,33 +24,53 @@ public override void HandleRequest(IZoneRequest request) var character = request.Session.Character; var player = request.Zone.GetPlayerOrThrow(character); + //Store ratios before transaction + var hpRatio = player.ArmorPercentage; + var apRatio = player.CorePercentage; + CheckPvpState(player).ThrowIfError(); CheckCombatState(player).ThrowIfError(); CheckActiveModules(player).ThrowIfError(); - player.EnlistTransaction(); + var containerEid = request.Data.GetOrDefault(k.containerEID); + var container = request.Zone.FindContainerOrThrow(player, containerEid); - 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); + CheckContainerType(container).ThrowIfError(); + CheckFieldTerminalRange(player, container).ThrowIfError(); - player.Initialize(character); - player.Save(); + player.EnlistTransaction(); + container.EnlistTransaction(); - Transaction.Current.OnCommited(() => + Transaction.Current.OnCompleted(completed => { + player.Initialize(character); + + //Apply original ratios after unequip-action + player.Armor = player.ArmorMax * hpRatio; + player.Core = player.CoreMax * apRatio; + player.SendRefreshUnitPacket(); var result = new Dictionary { - { k.robot, player.ToDictionary() } + {k.robot, player.ToDictionary()}, + {k.container, container.ToDictionary()} }; Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); }); - + + // Call equip/remove + DoChange(request, player, container); + + player.OnEquipChange(); + + player.Initialize(character); + player.CheckEnergySystemAndThrowIfFailed(); + + player.Save(); + container.Save(); + scope.Complete(); } } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 0160a171e..6396292cd 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -642,6 +642,11 @@ public virtual void OnAggression(Unit victim) { } + public void OnEquipChange() + { + _damageProcessor.OnRequipUnit(); + } + public double GetKersByDamageType(DamageType damageType) { switch (damageType) 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(); }); } From ef6b17457dc71b43527ffb0cf84f410e07914dd0 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Sat, 25 Jul 2020 14:01:13 -0400 Subject: [PATCH 196/457] finally caught up on this bug --- src/Perpetuum/Players/Player.cs | 1 + .../Zones/Teleporting/Strategies/TeleportWithinZone.cs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index b0527f910..ad9fb2d1f 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -315,6 +315,7 @@ public void SendModuleProcessError(Module module, ErrorCodes error) public void ApplyInvulnerableEffect() { + RemoveInvulnerableEffect(); // Remove existing effect, set new var builder = NewEffectBuilder().SetType(EffectType.effect_invulnerable); builder.WithDurationModifier(0.75); //Reduce span of syndicate protection ApplyEffect(builder); diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs index 087c79cff..6a108050e 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs @@ -59,8 +59,7 @@ public Task DoTeleportAsync(Player player) } if (ApplyInvulnerable) { - player.RemoveInvulnerableEffect(); // remove previous effect. - player.ApplyInvulnerableEffect(); // re-add effect to restart counter. + player.ApplyInvulnerableEffect(); } // mozoghat, fade out From 21be839a41df70dc9ee6eae595d93fa94fe76730 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Mon, 27 Jul 2020 02:26:20 -0400 Subject: [PATCH 197/457] cleanup and refactor request from review - beautiful! --- .../Zone/Containers/ZoneChangeModule.cs | 44 +++++++++---------- src/Perpetuum/Units/Unit.cs | 4 ++ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index 8b5b3c103..61afcc892 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -17,6 +17,12 @@ public abstract class ZoneChangeModule : ZoneContainerRequestHandler /// public abstract void DoChange(IZoneRequest request, Player player, Container container); + private static void OnCompleted(Player player, MessageBuilder message) + { + player.SendRefreshUnitPacket(); + message.Send(); + } + public override void HandleRequest(IZoneRequest request) { using (var scope = Db.CreateTransaction()) @@ -24,10 +30,6 @@ public override void HandleRequest(IZoneRequest request) var character = request.Session.Character; var player = request.Zone.GetPlayerOrThrow(character); - //Store ratios before transaction - var hpRatio = player.ArmorPercentage; - var apRatio = player.CorePercentage; - CheckPvpState(player).ThrowIfError(); CheckCombatState(player).ThrowIfError(); CheckActiveModules(player).ThrowIfError(); @@ -38,39 +40,35 @@ public override void HandleRequest(IZoneRequest request) CheckContainerType(container).ThrowIfError(); CheckFieldTerminalRange(player, container).ThrowIfError(); - player.EnlistTransaction(); container.EnlistTransaction(); - Transaction.Current.OnCompleted(completed => - { - player.Initialize(character); - - //Apply original ratios after unequip-action - player.Armor = player.ArmorMax * hpRatio; - player.Core = player.CoreMax * apRatio; - - player.SendRefreshUnitPacket(); - - var result = new Dictionary - { - {k.robot, player.ToDictionary()}, - {k.container, container.ToDictionary()} - }; - Message.Builder.FromRequest(request).WithData(result).WrapToResult().Send(); - }); + // 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.Save(); container.Save(); + var result = new Dictionary + { + {k.robot, player.ToDictionary()}, + {k.container, container.ToDictionary()} + }; + var message = Message.Builder.FromRequest(request).WithData(result).WrapToResult(); + Transaction.Current.OnCompleted(completed => OnCompleted(player, message)); + scope.Complete(); } } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 6396292cd..3b90ba79b 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -642,6 +642,10 @@ 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(); From 26058ec7b07d5ce101fdcd29a56d4a31a4c8f856 Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Mon, 27 Jul 2020 02:32:28 -0400 Subject: [PATCH 198/457] lint --- .../Zone/Containers/ZoneChangeModule.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index 61afcc892..38cc1cb57 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -58,14 +58,14 @@ public override void HandleRequest(IZoneRequest request) player.Armor = player.ArmorMax * hpRatio; player.Core = player.CoreMax * apRatio; + player.Save(); container.Save(); - var result = new Dictionary - { - {k.robot, player.ToDictionary()}, - {k.container, container.ToDictionary()} - }; + var result = new Dictionary { + { k.robot, player.ToDictionary() }, + { k.container, container.ToDictionary() } + }; var message = Message.Builder.FromRequest(request).WithData(result).WrapToResult(); Transaction.Current.OnCompleted(completed => OnCompleted(player, message)); From c829ab27f4a28477c14dca363aba74f59525c81f Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 27 Jul 2020 02:52:23 -0400 Subject: [PATCH 199/457] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml 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'] From b87cedd22a9b50370f27dc604fb2ff793c62103e Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Mon, 27 Jul 2020 15:36:33 -0400 Subject: [PATCH 200/457] even more refactors --- .../Zone/Containers/EquipModule.cs | 1 - .../Zone/Containers/ZoneChangeModule.cs | 105 ++++++++++++------ 2 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs index 8b548d2cd..d26a65ceb 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs @@ -5,7 +5,6 @@ using Perpetuum.Players; using Perpetuum.Robots; - namespace Perpetuum.RequestHandlers.Zone.Containers { public class EquipModule : ZoneChangeModule diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index 38cc1cb57..26ddd64d6 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Transactions; +using Perpetuum.Accounting.Characters; using Perpetuum.Containers; using Perpetuum.Data; using Perpetuum.Host.Requests; @@ -17,60 +18,96 @@ public abstract class ZoneChangeModule : ZoneContainerRequestHandler /// public abstract void DoChange(IZoneRequest request, Player player, Container container); - private static void OnCompleted(Player player, MessageBuilder message) - { - player.SendRefreshUnitPacket(); - message.Send(); - } - 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); - var containerEid = request.Data.GetOrDefault(k.containerEID); - var container = request.Zone.FindContainerOrThrow(player, containerEid); + var container = GetContainer(request, player); - CheckContainerType(container).ThrowIfError(); - CheckFieldTerminalRange(player, container).ThrowIfError(); + CheckContainer(player, container); - player.EnlistTransaction(); - container.EnlistTransaction(); + EquipChange(request, player, container, character); - // Store ratios before transaction - var hpRatio = player.ArmorPercentage; - var apRatio = player.CorePercentage; + SendClientMessageOncomplete(request, player, container); - // Call equip/remove - DoChange(request, player, container); - player.OnEquipChange(); + scope.Complete(); + } + } - player.Initialize(character); - player.CheckEnergySystemAndThrowIfFailed(); + private Player GetPlayer(IZoneRequest request, Character character) + { + return request.Zone.GetPlayerOrThrow(character); + } - // Apply ratios to prevent resetting values - player.Armor = player.ArmorMax * hpRatio; - player.Core = player.CoreMax * apRatio; + 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(); + } - player.Save(); - container.Save(); + private void CheckContainer(Player player, Container container) + { + CheckContainerType(container).ThrowIfError(); + CheckFieldTerminalRange(player, container).ThrowIfError(); + } - var result = new Dictionary { + 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; + + // 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() } }; - var message = Message.Builder.FromRequest(request).WithData(result).WrapToResult(); - Transaction.Current.OnCompleted(completed => OnCompleted(player, message)); + return Message.Builder.FromRequest(request).WithData(result).WrapToResult(); + } - scope.Complete(); - } + private static void OnCompleted(Player player, MessageBuilder message) + { + player.SendRefreshUnitPacket(); + message.Send(); } } } \ No newline at end of file From 65e1f29a9c29508036a7f8e9cfb4a29e1105f7ca Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 28 Jul 2020 14:12:15 -0400 Subject: [PATCH 201/457] typo on casing (#180) --- .../Zone/Containers/ZoneChangeModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index 26ddd64d6..ab68ab54c 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -33,7 +33,7 @@ public override void HandleRequest(IZoneRequest request) EquipChange(request, player, container, character); - SendClientMessageOncomplete(request, player, container); + SendClientMessageOnComplete(request, player, container); scope.Complete(); } @@ -88,7 +88,7 @@ private void EquipChange(IZoneRequest request, Player player, Container containe container.Save(); } - private void SendClientMessageOncomplete(IZoneRequest request, Player player, Container container) + private void SendClientMessageOnComplete(IZoneRequest request, Player player, Container container) { var message = BuildMessage(request, player, container); Transaction.Current.OnCompleted(completed => OnCompleted(player, message)); From 307770acfd3db25cd86ef302ef24bebf435d3401 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 30 Jul 2020 18:32:09 -0400 Subject: [PATCH 202/457] Resolve orphan players on zone (#178) * reckoning from zone to players and players-zonesession * refactored into standalone timeout handling class called in the zone's update call * whitespace * rollback edits to zonesesh * linq overhaul * update frequency of check and timeout * unused import * chore: rework session timeouts for contracted linq Signed-off-by: Daniel Smith * keyword on interface Co-authored-by: Daniel Smith --- src/Perpetuum/Comparers/EntityComparer.cs | 29 +++++ src/Perpetuum/EntityFramework/Entity.cs | 2 +- src/Perpetuum/EntityFramework/IEntity.cs | 8 ++ src/Perpetuum/Perpetuum.csproj | 6 +- .../Zones/SessionlessPlayerTimeout.cs | 115 ++++++++++++++++++ src/Perpetuum/Zones/Zone.cs | 9 +- 6 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 src/Perpetuum/Comparers/EntityComparer.cs create mode 100644 src/Perpetuum/EntityFramework/IEntity.cs create mode 100644 src/Perpetuum/Zones/SessionlessPlayerTimeout.cs 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/EntityFramework/Entity.cs b/src/Perpetuum/EntityFramework/Entity.cs index 0012ba81a..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; } 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 053742f45..e82ad575f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -78,7 +78,6 @@ ..\..\packages\System.Collections.Immutable.1.4.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - 3.5 @@ -93,7 +92,6 @@ - 3.0 @@ -1159,11 +1157,14 @@ + + + @@ -1206,6 +1207,7 @@ + diff --git a/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs new file mode 100644 index 000000000..7643240d0 --- /dev/null +++ b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs @@ -0,0 +1,115 @@ +using Perpetuum.Comparers; +using Perpetuum.EntityFramework; +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); + RemoveExpiredOrphansFromZone(_orphanPlayers); + } + + private void PopulateNewSessionlessPlayers(IZone zone) + { + var newOrphans = + zone.Players + .Where(p => p.Session == ZoneSession.None) + .Cast() + .Except(_orphanPlayers, new EntityComparer()) + .Cast() + .Select(p => new PlayerTimeout(p)); + + _orphanPlayers.AddRange(newOrphans); + } + + private void RemoveExpiredOrphansFromZone(IEnumerable 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(); + + _orphanPlayers.RemoveAll(p => p.Expired); + } + + private class PlayerTimeout : IEntity + { + private readonly TimeKeeper _time; + private Player Player { get; } + + 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() + { + 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/Zone.cs b/src/Perpetuum/Zones/Zone.cs index a553c2f87..112d458cc 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -9,7 +9,6 @@ using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers; using Perpetuum.EntityFramework; -using Perpetuum.ExportedTypes; using Perpetuum.Groups.Corporations; using Perpetuum.Groups.Gangs; using Perpetuum.Log; @@ -24,7 +23,6 @@ using Perpetuum.Zones.Beams; using Perpetuum.Zones.Blobs; using Perpetuum.Zones.Decors; -using Perpetuum.Zones.Effects; using Perpetuum.Zones.Environments; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; @@ -80,13 +78,16 @@ public abstract class Zone : Threading.Process.Process,IZone public TcpListener Listener { get; set; } - protected Zone(ISessionManager sessionManager,IGangManager gangManager) + private readonly SessionlessPlayerTimeout _sessionlessPlayerTimeout; + + protected Zone(ISessionManager sessionManager, IGangManager gangManager) { 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) @@ -155,6 +156,8 @@ private void UpdateSessions(TimeSpan time) { session.Update(time); } + + _sessionlessPlayerTimeout.Update(time); } [CanBeNull] From e0ef27bac0c72cf399bcd30af48831be626607ce Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 1 Aug 2020 15:24:59 -0400 Subject: [PATCH 203/457] Npc boss aggro chat + refactor (#185) * closes: #184 * deaggro hook --- .../EventServices/EventProcessors/ChatEcho.cs | 4 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 107 +++++++++--------- src/Perpetuum/Zones/NpcSystem/Npc.cs | 7 +- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs index fa005ee89..c3c27c1a5 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs @@ -39,14 +39,12 @@ public override void OnNext(EventMessage value) /// public class NpcChatEcho : EventProcessor { - private readonly IChannelManager _channelManager; private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; //TODO "Nian" character private Character _announcer; - public NpcChatEcho(IChannelManager channelManager) + public NpcChatEcho() { _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); - _channelManager = channelManager; } public override void OnNext(EventMessage value) diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index 672d8d019..aa10201f9 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -48,29 +48,40 @@ public static NpcBossInfo GetBossInfoByFlockID(int flockid) private readonly int _id; private readonly int _flockid; private readonly double? _respawnNoiseFactor; - private readonly bool _lootSplit; private readonly long? _outpostEID; private readonly int? _stabilityPts; - private readonly bool _overrideRelations; private readonly string _deathMsg; private readonly string _aggroMsg; - private bool _sayOnce; + private bool _speak; + + private bool IsOutpostBoss { get { return _outpostEID != null; } } + private int StabilityPoints { get { return _stabilityPts ?? 0; } } + private bool OverrideRelations { get; } + + public bool IsLootSplit { get; } public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg) { _id = id; _flockid = flockid; _respawnNoiseFactor = respawnNoiseFactor; - _lootSplit = lootSplit; + IsLootSplit = lootSplit; _outpostEID = outpostEID; _stabilityPts = stabilityPts; - _overrideRelations = overrideRelations; + OverrideRelations = overrideRelations; _deathMsg = customDeathMsg; _aggroMsg = customAggroMsg; - _sayOnce = true; + _speak = true; + } + + /// + /// Handle any actions when the boss loses aggro + /// + public void OnDeAggro() + { + _speak = true; } - // TODO - find good place to call this that will be stable and not noisey (excessive repeat messages) /// /// Handle any actions that this NPC Boss should do upon Aggression, including sending a message /// @@ -78,15 +89,8 @@ public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSpl /// the npc event channel public void OnAggro(Player aggressor, EventListenerService channel) { - if (_sayOnce) - { - _sayOnce = false; - CommunicateAggression(aggressor, channel); - } - if (_outpostEID != null) - { - aggressor.ApplyPvPEffect(); - } + CommunicateAggression(aggressor, channel); + HandleBossOutpostAggro(aggressor); } /// @@ -99,65 +103,64 @@ public void OnAggro(Player aggressor, EventListenerService channel) public void OnDeath(Npc npc, Unit killer, EventListenerService channel) { CommunicateDeath(killer, channel); - if (_outpostEID != null) - { - 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); - channel.PublishMessage(builder.Build()); - } - } + HandleBossOutpostDeath(npc, killer, channel); } /// - /// Apply any respawn timer modifiers + /// Apply any respawn timer modifiers, and reset any internal state on-respawn /// /// normal respawn time of npc /// modified respawn time of npc public TimeSpan OnRespawn(TimeSpan respawnTime) { + _speak = true; var factor = _respawnNoiseFactor ?? 0.0; return respawnTime.Multiply(FastRandom.NextDouble(1.0 - factor, 1.0 + factor)); } - /// - /// True if this NPC Boss should use auto-loot splitting - /// - /// true - if loot needs splitting - public bool IsLootSplit() + private void HandleBossOutpostAggro(Player aggressor) { - return _lootSplit; + if (IsOutpostBoss) + { + aggressor.ApplyPvPEffect(); + } } - private bool OverrideRelations() + private void CommunicateAggression(Unit aggressor, EventListenerService channel) { - return _overrideRelations; + if (_speak) + { + _speak = false; + SendMessage(aggressor, channel, _aggroMsg); + } } - private int StabilityPoints() + private void CommunicateDeath(Unit aggressor, EventListenerService channel) { - return _stabilityPts ?? 0; + SendMessage(aggressor, channel, _deathMsg); } - private void CommunicateAggression(Unit aggressor, EventListenerService channel) + private void HandleBossOutpostDeath(Npc npc, Unit killer, EventListenerService channel) { - SendMessage(aggressor, channel, _aggroMsg); - } + if (!IsOutpostBoss) + return; - private void CommunicateDeath(Unit aggressor, EventListenerService channel) - { - SendMessage(aggressor, channel, _deathMsg); + 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); + channel.PublishMessage(builder.Build()); + } } private static void SendMessage(Unit src, EventListenerService eventChannel, string msg) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 13eae3436..591674281 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -260,6 +260,11 @@ public override void Enter() _movement.Start(npc); }); + if (npc.IsBoss()) + { + npc.BossInfo.OnDeAggro(); + } + base.Enter(); } @@ -811,7 +816,7 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) using (var scope = Db.CreateTransaction()) { - if (IsBoss() && BossInfo.IsLootSplit()) + if (IsBoss() && BossInfo.IsLootSplit) { //Boss - Split loot equally to all participants List participants = new List(); From d04c56b77ced875b6cb51e9086e8233f440ffe45 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 1 Aug 2020 15:25:24 -0400 Subject: [PATCH 204/457] Save Layers (#183) * save layers command to save on demand * rename var * YEP DRY --- .../ZoneSaveLayer.cs | 30 ++++++++++++++----- .../Services/Channels/GameAdminCommands.cs | 20 ++++++++++++- 2 files changed, 41 insertions(+), 9 deletions(-) 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/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 5ba6817b0..91cc6e2ec 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -948,7 +948,25 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C } - + if (command[0] == "#savelayers") + { + bool err = false; + var dictionary = new Dictionary(); + if (command.Length == 2) + { + err = !int.TryParse(command[1], out int zoneId); + if (err) + { + channel.SendMessageToAll(sessionManager, sender, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + dictionary.Add(k.zoneID, zoneId); + } + var cmd = string.Format("{0}:relay:{1}", Commands.ZoneSaveLayer.Text, GenxyConverter.Serialize(dictionary)); + channel.SendMessageToAll(sessionManager, sender, $"SaveLayers command accepted: {dictionary.ToDebugString()} \r\nSaving... "); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + channel.SendMessageToAll(sessionManager, sender, $"Layer(s) Saved! "); + } } } } From 18d1ee0aff5e33b7dfa4dad240376460c36ce5c0 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 10 Aug 2020 14:00:36 -0400 Subject: [PATCH 205/457] check state before teleport (#187) --- .../Strategies/TeleportWithinZone.cs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TeleportWithinZone.cs index 6a108050e..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,23 +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) + if (ApplyTeleportSickness) + { + player.ApplyTeleportSicknessEffect(); + } + if (ApplyInvulnerable) + { + player.ApplyInvulnerableEffect(); + } + } + catch (Exception ex) { - player.ApplyTeleportSicknessEffect(); + Logger.Exception(ex); } - if (ApplyInvulnerable) + finally { - player.ApplyInvulnerableEffect(); + // mozoghat, fade out + player.States.InMoveable = false; + player.States.LocalTeleport = false; } - // mozoghat, fade out - player.States.InMoveable = false; - player.States.LocalTeleport = false; }); return task; From d721f9fc8f61ddcbd42341e27ca8305b82a17330 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 10 Aug 2020 14:00:49 -0400 Subject: [PATCH 206/457] sap timer extend if started before expires (#188) --- src/Perpetuum/Timers/TimeTracker.cs | 5 +++ src/Perpetuum/Zones/Intrusion/SAP.cs | 55 ++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/Perpetuum/Timers/TimeTracker.cs b/src/Perpetuum/Timers/TimeTracker.cs index 7720d3685..01c156887 100644 --- a/src/Perpetuum/Timers/TimeTracker.cs +++ b/src/Perpetuum/Timers/TimeTracker.cs @@ -36,6 +36,11 @@ public TimeSpan Remaining get { return Duration - Elapsed; } } + public void Extend(TimeSpan extraDuration) + { + Duration += extraDuration; + } + public virtual void Reset() { Elapsed = TimeSpan.Zero; diff --git a/src/Perpetuum/Zones/Intrusion/SAP.cs b/src/Perpetuum/Zones/Intrusion/SAP.cs index 2d18f3702..082e972ee 100644 --- a/src/Perpetuum/Zones/Intrusion/SAP.cs +++ b/src/Perpetuum/Zones/Intrusion/SAP.cs @@ -10,6 +10,7 @@ using Perpetuum.Log; using Perpetuum.Players; using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Threading; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Beams; @@ -51,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; @@ -71,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) @@ -94,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(); @@ -119,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); } @@ -167,7 +175,6 @@ protected void RemovePlayerInfo(Character character) } } - protected void IncrementPlayerScore(Player player, int score) { player.ApplyPvPEffect(); @@ -182,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; @@ -237,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; } From dd79293051c4e69f719d03cced830588c4e154c5 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 12 Aug 2020 09:46:23 -0400 Subject: [PATCH 207/457] Sessionless-fix Fix (#201) * should clean the list of connected players * adjust params --- .../Zones/SessionlessPlayerTimeout.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs index 7643240d0..b9acb7dde 100644 --- a/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs +++ b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs @@ -37,11 +37,12 @@ public void Update(TimeSpan time) private void DoUpdate() { - PopulateNewSessionlessPlayers(_zone); + PopulateNewSessionlessPlayers(_zone, _orphanPlayers); + CleanValidSessions(_orphanPlayers); RemoveExpiredOrphansFromZone(_orphanPlayers); } - private void PopulateNewSessionlessPlayers(IZone zone) + private void PopulateNewSessionlessPlayers(IZone zone, List orphans) { var newOrphans = zone.Players @@ -51,10 +52,16 @@ private void PopulateNewSessionlessPlayers(IZone zone) .Cast() .Select(p => new PlayerTimeout(p)); - _orphanPlayers.AddRange(newOrphans); + orphans.AddRange(newOrphans); } - private void RemoveExpiredOrphansFromZone(IEnumerable orphans) + + private void CleanValidSessions(List orphans) + { + orphans.RemoveAll(p => !p.Sessionless); + } + + private void RemoveExpiredOrphansFromZone(List orphans) { var toRemove = orphans.Where(o => o.Expired); @@ -63,7 +70,7 @@ private void RemoveExpiredOrphansFromZone(IEnumerable orphans) foreach (var orphan in toRemove) orphan.RemoveFromZone(); - _orphanPlayers.RemoveAll(p => p.Expired); + orphans.RemoveAll(p => p.Expired); } private class PlayerTimeout : IEntity @@ -71,6 +78,7 @@ 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; } } From e698389943d0b3c2a963264f195b7ebf6752cc94 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 Aug 2020 13:51:01 -0400 Subject: [PATCH 208/457] wow that was easier than I thought (#206) --- .../PerpetuumBootstrapper.cs | 7 ++--- .../Modules/Weapons/FirearmWeaponModule.cs | 26 +++++++++++++++++++ src/Perpetuum/Modules/Weapons/WeaponModule.cs | 5 ++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index cf20f3adb..c70869642 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -926,6 +926,7 @@ private void RegisterEntities() RegisterModule(); RegisterModule(); RegisterModule(); + RegisterModule(); // OPP: new subclass for firearms RegisterModule(); RegisterModule(); RegisterModule(); @@ -1145,9 +1146,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_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)); diff --git a/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs new file mode 100644 index 000000000..f15773688 --- /dev/null +++ b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs @@ -0,0 +1,26 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Units; + +namespace Perpetuum.Modules.Weapons +{ + public class FirearmWeaponModule : WeaponModule + { + + public FirearmWeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags) + { + } + + 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/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 6b969fdac..c61f39868 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -26,6 +26,11 @@ public ModuleProperty DamageModifier get { return _damageModifier; } } + public ModuleProperty Accuracy + { + get { return _accuracy; } + } + public WeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) { _action = new ModuleAction(this); From e2136985d40fcab847f8ede3265865f1e238a8b7 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 Aug 2020 13:51:36 -0400 Subject: [PATCH 209/457] mission buffs and return teleport removed for beta/gamma (#208) --- .../MissionTargets/MissionTargetRewardCalculator.cs | 2 +- .../MissionTargets/RandomTargetObjects.cs | 4 ++-- .../MissionEngine/MissionTargets/ZoneMissionTarget.cs | 2 +- .../MissionEngine/Missions/MissionInProgress.cs | 10 +++------- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index 2bfbde642..0b5c94fd4 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -68,7 +68,7 @@ private void PayAsMineral() } //ECONOMY! TODO put this in DB! - public static readonly double[] PayOutMultipliers = new double[] { 75, 125, 200, 300, 450, 600, 700, 800, 900, 1000 }; + public static readonly double[] PayOutMultipliers = new double[] { 100, 175, 250, 350, 475, 600, 700, 800, 900, 1000 }; private void PayReward() { diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs index 82bf7152e..513bb27c1 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) { 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 316b2806c..78e3b2c7e 100644 --- a/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs +++ b/src/Perpetuum/Services/MissionEngine/Missions/MissionInProgress.cs @@ -2100,18 +2100,14 @@ public List CollectActiveCPRGDefinitions() } public const int MISSION_SUCCESS_TELEPORT_MINUTES = 5; - public const double MISSION_SUCCESS_TELEPORT_MINUTES_BETA = 2.5; - public void EnableTeleportOnSuccess(IEnumerable characters, int zoneId) { var zone = _zoneManager.GetZone(myLocation.ZoneConfig.Id); - if (zone.Configuration.IsBeta) - { - //on beta, only the owner, shorter timer - zone.GetPlayer(character)?.EnableSelfTeleport(TimeSpan.FromMinutes(MISSION_SUCCESS_TELEPORT_MINUTES_BETA),zoneId); + + // Not an option on beta/gamma + if (!zone.Configuration.Protected) return; - } foreach (var currentCharacter in characters) { From 12280f1e694b9b850010dbd0e67759496c7553da Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 Aug 2020 15:10:10 -0400 Subject: [PATCH 210/457] Stronghold features (#191) * stronghold exit rift system HUZZAH! * embrace invulnerability, refactors, better inheritance and DRY, zoneriftconfigs in db, rift params no longer hardcoded * cleanup * teleportdevice to zone mapping, for stronghold teleport items * new line at end-of-files, cache everything on startup, no fancy passthru cache, sad * cleanup old TODO comments --- .../PerpetuumBootstrapper.cs | 36 +++-- .../DefinitionNames.cs | 1 + src/Perpetuum/Perpetuum.csproj | 7 +- .../Services/RiftSystem/IRiftManager.cs | 9 ++ src/Perpetuum/Services/RiftSystem/Rift.cs | 124 +++++++----------- .../Services/RiftSystem/RiftManager.cs | 23 +--- .../StrongholdExitLocations.cs | 48 +++++++ .../StrongholdRifts/StrongholdExitRift.cs | 40 ++++++ .../StrongholdRifts/StrongholdRiftManager.cs | 55 ++++++++ .../Services/RiftSystem/ZoneRiftConfigs.cs | 49 +++++++ .../Presences/DynamicPoolPresence.cs | 2 +- .../Teleporting/MobileStrongholdTeleport.cs | 4 +- .../Teleporting/MobileTeleportToZoneMap.cs | 46 +++++++ .../StrongholdTeleportTargetHelper.cs | 12 +- src/Perpetuum/Zones/Zone.cs | 2 +- 15 files changed, 346 insertions(+), 112 deletions(-) create mode 100644 src/Perpetuum/Services/RiftSystem/IRiftManager.cs create mode 100644 src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs create mode 100644 src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs create mode 100644 src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs create mode 100644 src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs create mode 100644 src/Perpetuum/Zones/Teleporting/MobileTeleportToZoneMap.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index c70869642..5b3dd22a7 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -102,6 +102,7 @@ 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; @@ -858,7 +859,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); - RegisterEntity();//TODO new CT Capsule! + RegisterEntity(); // OPP: new CT Capsule item RegisterProductionFacility(); RegisterProductionFacility(); @@ -948,7 +949,7 @@ private void RegisterEntities() RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); - RegisterEffectModule(); //TODO new module + RegisterEffectModule(); //OPP: NPC-only module for detection debuff RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); @@ -967,7 +968,7 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit(); RegisterUnit(); - RegisterUnit(); // New Field device + RegisterUnit(); // OPP: New mobile tele for entry to Strongholds RegisterUnit(); RegisterUnit(); RegisterPBSObject(); @@ -994,6 +995,7 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit(); RegisterUnit(); + RegisterUnit(); // OPP: Special rift for exiting strongholds RegisterEntity(); RegisterEntity(); @@ -1014,7 +1016,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); - RegisterEntity();//TODO register new entitydef + RegisterEntity(); // OPP: Robot paint item RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -1045,7 +1047,8 @@ void ByName(string name,params Parameter[] parameters) where T : Entity ByDefinition(ed.Definition, parameters); } - //TODO: new for paint + //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 @@ -1218,7 +1221,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); // TODO new field dev + 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); @@ -1227,7 +1230,8 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_lottery_items); //TODO ORDER MATTERS! Register Paints AFTER lottery will ensure Paint objects are valid subset of lottery category - //TODO entitydefaults must contain name "def_paint" and have cf_lottery_items + //TODO entitydefaults must contain name "def_paint" and have cf_lottery_items + //TODO make separate category ByNamePatternAndFlag("def_paint", CategoryFlags.cf_lottery_items); //TODO new CalibrationTemplateItem -- activates like paint! same category! @@ -1302,6 +1306,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByName(DefinitionNames.RANDOM_RIFT_PORTAL); ByName(DefinitionNames.BASE_ITEM_SHOP); ByName(DefinitionNames.ANNIVERSARY_PACKAGE); + ByName(DefinitionNames.STRONGHOLD_EXIT_RIFT); var c = b.Build(); @@ -1673,13 +1678,13 @@ private void InitRelayManager() _builder.RegisterType(); - //TODO new EPBonusEventService + // OPP: EPBonusEventService singleton _builder.RegisterType().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1))); }); - //TODO new EventListenerService + // OPP: EventListenerService and consumers _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); @@ -1691,7 +1696,7 @@ private void InitRelayManager() e.Instance.AttachListener(e.Context.Resolve()); }); - //TODO new InterzoneNPCManager + // OPP: InterzoneNPCManager RegisterAutoActivate(TimeSpan.FromSeconds(10)); _builder.RegisterType().As(); @@ -2387,8 +2392,9 @@ private void RegisterRifts() }); _builder.RegisterType(); + _builder.RegisterType(); - _builder.Register>(x => + _builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2396,6 +2402,11 @@ private void RegisterRifts() if (zone is TrainingZone) return null; + if (zone is StrongHoldZone) + { + return ctx.Resolve(new TypedParameter(typeof(IZone), zone)); + } + 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)); @@ -2508,7 +2519,7 @@ 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.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); @@ -2566,6 +2577,7 @@ void RegisterZone(ZoneType type) where T:Zone _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType().As().SingleInstance(); _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 7225cb6bd..0bd941f45 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4719,6 +4719,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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index e82ad575f..3adb2ebe5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -318,6 +318,11 @@ + + + + + @@ -897,6 +902,7 @@ + @@ -1207,7 +1213,6 @@ - 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 e47d55fb7..b917fadef 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -9,16 +9,30 @@ using Perpetuum.Players; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Units; -using Perpetuum.Units.DockingBases; using Perpetuum.Zones; using Perpetuum.Zones.Blobs.BlobEmitters; using Perpetuum.Zones.NpcSystem.Presences; -using Perpetuum.Zones.Teleporting; using Perpetuum.Zones.Teleporting.Strategies; namespace Perpetuum.Services.RiftSystem { - public abstract class Portal : Unit,IUsableItem + /// + /// 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); + } + } + + /// + /// Rifts that despawn + /// + public abstract class DespawningPortal : Portal { private UnitDespawnHelper _despawnHelper; @@ -32,16 +46,12 @@ protected override void OnUpdate(TimeSpan time) _despawnHelper?.Update(time, this); base.OnUpdate(time); } - - public virtual void UseItem(Player player) - { - player.HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.TeleportTimerStillRunning); - player.HasPvpEffect.ThrowIfTrue(ErrorCodes.CantBeUsedInPvp); - player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); - } } - public class RandomRiftPortal : Portal + /// + /// RandomRift (used as the Rift Echo after a TAP) + /// + public class RandomRiftPortal : DespawningPortal { private readonly ITeleportStrategyFactories _teleportStrategyFactories; private Position _targetPosition; @@ -73,7 +83,7 @@ public override void UseItem(Player player) teleport.DoTeleportAsync(player); } } - + public class RiftNpcGroupInfo { public int presenceID; @@ -82,6 +92,9 @@ public class RiftNpcGroupInfo public Player ownerPlayer; } + /// + /// TAP deployment + /// public class RiftActivator : ItemDeployerBase { public override void Deploy(IZone zone, Player player) @@ -90,73 +103,54 @@ public override void Deploy(IZone zone, Player player) if (rift == null) throw new PerpetuumException(ErrorCodes.RiftOutOfRange); - var maxRiftLevel = 1; - - if (zone.Configuration.IsAlpha) - { - maxRiftLevel = 1; - } - else if (zone.Configuration.IsBeta) - { - maxRiftLevel = 2; - if (zone.Configuration.Id > 8 && zone.Configuration.Id < 12) - { - maxRiftLevel = 3; //TODO fixme move to DB! - Logger.Info("LEVEL 3 RIFT ON BETA!"); - } - } - else if (zone.Configuration.IsGamma) - { - maxRiftLevel = 3; - } - - - if (ED.Tier.level > maxRiftLevel) + if (ED.Tier.level > rift.MaxTAPLevel) throw new PerpetuumException(ErrorCodes.RiftLevelMismatch); if (zone is StrongHoldZone) throw new PerpetuumException(ErrorCodes.ItemNotUsable); - var 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); + var 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); } @@ -192,44 +186,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); - + base.UseItem(player); - // we are on a stronghold. we want to go home. - // Sending them at the Hershfield main terminal. - if (player.Zone is StrongHoldZone) - { - var destZone = player.Character.GetZone(8); // Hershfield zone - var dockingBase = destZone.Units.OfType().First(); - var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(destZone); - teleport.TargetPosition = UndockSpawnPositionSelector.SelectSpawnPosition(dockingBase); - teleport.DoTeleportAsync(player); - return; - } - else + var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); + if (nearestRift == null) { - // Regular jump behaviour - player.CurrentPosition.IsInRangeOf3D(CurrentPosition, 8).ThrowIfFalse(ErrorCodes.TeleportOutOfRange); - - var nearestRift = Zone.Units.OfType().Where(rift => rift != this).GetNearestUnit(CurrentPosition); - if (nearestRift == null) - { - throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); - } - - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); - teleport.TargetPosition = nearestRift.CurrentPosition; - teleport.ApplyTeleportSickness = true; - teleport.ApplyInvulnerable = true; - teleport.DoTeleportAsync(player); + throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); } + + var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); + teleport.TargetPosition = nearestRift.CurrentPosition; + teleport.ApplyTeleportSickness = true; + teleport.ApplyInvulnerable = true; + 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 2d42a9e6c..f3121bbe6 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Linq; using System.Threading; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -56,8 +55,9 @@ 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; @@ -68,6 +68,7 @@ public class RiftManager public RiftManager(IZone zone, TimeRange spawnTime, RiftSpawnPositionFinder spawnPositionFinder, IEntityServices entityServices) { _zone = zone; + _config = ZoneRiftConfigReader.GetForZone(zone); _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; @@ -77,14 +78,7 @@ public RiftManager(IZone zone, TimeRange spawnTime, RiftSpawnPositionFinder spaw public void Update(TimeSpan time) { - while (_riftCounts < 10 && !(_zone is StrongHoldZone)) - { - _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); - Interlocked.Increment(ref _riftCounts); - } - - //If exit rift dies on stronghold, respawn it - if (_riftCounts < 1 && _zone is StrongHoldZone) + while (_riftCounts < _config.MaxRifts) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); @@ -106,16 +100,11 @@ private void SpawnRift() var rift = (Rift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.RIFT); rift.SetDespawnTime(TimeSpan.FromHours(3)); + rift.SetLevel(_config.MaxLevel); rift.RemovedFromZone += OnRiftRemovedFromZone; var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); - if (_zone is StrongHoldZone) - { - //TODO: Fixme for I should be a DB table for valid spawn locations on strongholds - spawnPosition = new Position(1120, 1039); - } - rift.AddToZone(_zone, spawnPosition, ZoneEnterType.NpcSpawn); Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2})", _zone.Id, rift.ED.Name, rift.CurrentPosition)); } @@ -125,4 +114,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..9d15044d1 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs @@ -0,0 +1,48 @@ +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) + { + var locations = Db.Query().CommandText("SELECT id, zoneid, x, y FROM strongholdexitconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute() + .Select((record) => + { + var x = record.GetValue("x"); + var y = record.GetValue("y"); + return new StrongholdRiftLocation(zone, new Position(x, y)); + }); + + return locations; + } + } + + /// + /// Spawn of StrongholdExitRift + /// + public class StrongholdRiftLocation + { + public IZone Zone { get; private set; } + public Position Location { get; private set; } + + public StrongholdRiftLocation(IZone zone, Position location) + { + Zone = zone; + Location = location; + } + } +} diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs new file mode 100644 index 000000000..0c45b0ecd --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs @@ -0,0 +1,40 @@ +using Perpetuum.Players; +using Perpetuum.Units.DockingBases; +using Perpetuum.Zones; +using Perpetuum.Zones.Teleporting.Strategies; +using System.Linq; + +namespace Perpetuum.Services.RiftSystem.StrongholdRifts +{ + /// + /// Special Rift to provide exit-jump home but not behave like other Rifts + /// Entitydefault configured to be invulnerable and not despawn + /// + public class StrongholdExitRift : Portal + { + private readonly ITeleportStrategyFactories _teleportStrategyFactories; + private readonly IZone _destinationZone; + private DockingBase _base; + + public StrongholdExitRift(ITeleportStrategyFactories teleportStrategyFactories, IZoneManager zoneManager) + { + _teleportStrategyFactories = teleportStrategyFactories; + _destinationZone = zoneManager.GetZone(8); + } + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + _base = _destinationZone.Units.OfType().First(); + base.OnEnterZone(zone, enterType); + } + + public override void UseItem(Player player) + { + base.UseItem(player); + + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(_destinationZone); + teleport.TargetPosition = UndockSpawnPositionSelector.SelectSpawnPosition(_base); + teleport.DoTeleportAsync(player); + } + } +} diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs new file mode 100644 index 000000000..935623b84 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs @@ -0,0 +1,55 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +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 IEntityServices _entityServices; + private readonly IEnumerable _spawnLocations; + + public StrongholdRiftManager(IZone zone, IEntityServices entityServices) + { + _zone = zone; + _spawnLocations = StrongholdRiftLocationRepository.GetAllInZone(zone); + _entityServices = entityServices; + } + + 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) + { + var rift = (StrongholdExitRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.STRONGHOLD_EXIT_RIFT); + rift.AddToZone(_zone, spawn.Location, ZoneEnterType.NpcSpawn); + Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2})", _zone.Id, rift.ED.Name, rift.CurrentPosition)); + } + } + +} diff --git a/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs b/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs new file mode 100644 index 000000000..61763c487 --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs @@ -0,0 +1,49 @@ +using Perpetuum.Data; +using Perpetuum.Zones; +using System.Linq; + +namespace Perpetuum.Services.RiftSystem +{ + /// + /// DB query for ZoneRiftConfigs + /// + public static class ZoneRiftConfigReader + { + /// + /// Get the ZoneRiftConfig for a Zone + /// + /// IZone + /// ZoneRiftConfig + public static ZoneRiftConfig GetForZone(IZone zone) + { + var riftConfigs = Db.Query().CommandText("SELECT TOP 1 id, zoneid, maxrifts, maxlevel FROM zoneriftsconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute() + .Select((record) => + { + var maxRifts = record.GetValue("maxrifts"); + var maxLevel = record.GetValue("maxlevel"); + return new ZoneRiftConfig(zone, maxRifts, maxLevel); + }); + + return riftConfigs.FirstOrDefault(); + } + } + + /// + /// Zone specs for number of rifts and TAP level + /// + public class ZoneRiftConfig + { + public IZone Zone { get; private set; } + public int MaxRifts { get; private set; } + public int MaxLevel { get; private set; } + public ZoneRiftConfig(IZone zone, int maxRifts, int maxLevel) + { + Zone = zone; + MaxRifts = maxRifts; + MaxLevel = maxLevel; + } + } +} + diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs index 1de611f5d..03a34bdec 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPoolPresence.cs @@ -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/Teleporting/MobileStrongholdTeleport.cs b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs index f6b6cba31..235337440 100644 --- a/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs +++ b/src/Perpetuum/Zones/Teleporting/MobileStrongholdTeleport.cs @@ -25,7 +25,7 @@ public override IEnumerable GetTeleportDescriptions() if (_usedTeleportDescription != null) return new[] { _usedTeleportDescription }; - return _strongholdTeleportTargetHelper.GetStrongholdTargets(Zone, Eid, TeleportRange); + return _strongholdTeleportTargetHelper.GetStrongholdTargets(Zone, Eid, TeleportRange, Definition); } public override Dictionary ToDictionary() @@ -52,7 +52,7 @@ public void Activate(Player player, TeleportDescription description) public override void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) { - var testlist = _strongholdTeleportTargetHelper.GetStrongholdTargets(Zone, Eid, TeleportRange).ToList(); + var testlist = _strongholdTeleportTargetHelper.GetStrongholdTargets(Zone, Eid, TeleportRange, Definition).ToList(); (testlist.Count == 0).ThrowIfTrue(ErrorCodes.NoTeleportTargetsWereFound); 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/StrongholdTeleportTargetHelper.cs b/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs index 33f1b7a34..36bceb836 100644 --- a/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs +++ b/src/Perpetuum/Zones/Teleporting/StrongholdTeleportTargetHelper.cs @@ -7,20 +7,24 @@ public class StrongholdTeleportTargetHelper { private readonly IZoneManager _zoneManager; private readonly TeleportDescriptionBuilder.Factory _descriptionBuilderFactory; + private readonly IMobileTeleportToZoneMap _mobileTeleportToZoneMap; - public StrongholdTeleportTargetHelper(IZoneManager zoneManager, TeleportDescriptionBuilder.Factory descriptionBuilderFactory) + public StrongholdTeleportTargetHelper(IZoneManager zoneManager, TeleportDescriptionBuilder.Factory descriptionBuilderFactory, IMobileTeleportToZoneMap mobileTeleportToZoneMap) { _zoneManager = zoneManager; _descriptionBuilderFactory = descriptionBuilderFactory; + _mobileTeleportToZoneMap = mobileTeleportToZoneMap; } - public IEnumerable GetStrongholdTargets(IZone zone, long sourceTeleportEid, int teleportRange) + public IEnumerable GetStrongholdTargets(IZone zone, long sourceTeleportEid, int teleportRange, int mobileDefinition) { var result = new List(); var descriptionId = 0; - foreach (var teleportColumn in _zoneManager.Zones.OfType().GetUnits().Where(tc => tc.IsEnabled && tc.Zone != zone)) + 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) diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 112d458cc..194f1322c 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -65,7 +65,7 @@ public abstract class Zone : Threading.Process.Process,IZone public ZoneSession.Factory ZoneSessionFactory { get; set; } [CanBeNull] - public RiftManager RiftManager { private get; set; } + public IRiftManager RiftManager { private get; set; } [CanBeNull] public IRelicManager RelicManager { get; set; } From 44f888bdc1c428087e0a3156bb00cc420170b8e4 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 Aug 2020 15:47:40 -0400 Subject: [PATCH 211/457] stuff getting dropped out of this file from merges? idk (#209) --- src/Perpetuum/Perpetuum.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 3adb2ebe5..7f663ac85 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -816,6 +816,7 @@ + @@ -1213,6 +1214,7 @@ + From 10055cc170bff46198562156eab841a4bfc9780f Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 Aug 2020 15:50:30 -0400 Subject: [PATCH 212/457] Plant-only damage type (#205) * new damage type that exclusively affects plants * wow that was easier than I thought * streamline plant damage logic * add comparers folder back --- src/Perpetuum.ExportedTypes/AggregateField.cs | 4 +- .../EntityFramework/DefinitionConfig.cs | 3 ++ src/Perpetuum/Keywords.cs | 3 ++ src/Perpetuum/Modules/Weapons/Damage.cs | 35 +++++++++++------ src/Perpetuum/Modules/Weapons/DamageType.cs | 3 +- .../Modules/Weapons/FirearmWeaponModule.cs | 13 +++++-- src/Perpetuum/Modules/Weapons/WeaponAmmo.cs | 18 +++++++-- src/Perpetuum/Modules/Weapons/WeaponModule.cs | 38 +++++++------------ src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Zones/Eggs/AreaBomb.cs | 1 + .../Zones/Terrains/TerrainExtensions.cs | 31 +++++++-------- 11 files changed, 87 insertions(+), 63 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 31ded26f3..48aa9fe52 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -165,7 +165,9 @@ public enum AggregateField damage_small_projectile_modifier = 154, damage_small_railgun_modifier = 155, damage_thermal = 156, - damage_turret_modifier = 158, + damage_toxic = 660, + damage_toxic_modifier = 661, + damage_turret_modifier = 158, decay_chance = 613, default_effect_range = 585, despawn_time = 580, diff --git a/src/Perpetuum/EntityFramework/DefinitionConfig.cs b/src/Perpetuum/EntityFramework/DefinitionConfig.cs index 125f6457e..8f01bde5e 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"); @@ -161,6 +163,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/Keywords.cs b/src/Perpetuum/Keywords.cs index c63203e5b..dd2967afa 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -258,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"; 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 index f15773688..8f05e7ff7 100644 --- a/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs @@ -1,15 +1,20 @@ -using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Items; 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) @@ -19,8 +24,8 @@ protected override bool CheckAccuracy(Unit victim) protected override IDamageBuilder GetDamageBuilder() { - return base.GetDamageBuilder().WithExplosionRadius(Accuracy.Value); + return base.GetDamageBuilder() + .WithExplosionRadius(Accuracy.Value); } - } } diff --git a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs index 7d3a3b78a..a959a65b3 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs @@ -30,17 +30,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); diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index c61f39868..5005644d6 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -18,26 +17,16 @@ public class WeaponModule : ActiveModule { private readonly ModuleAction _action; - private readonly ModuleProperty _damageModifier; - private readonly ModuleProperty _accuracy; - - public ModuleProperty DamageModifier - { - get { return _damageModifier; } - } - - public ModuleProperty Accuracy - { - get { return _accuracy; } - } + public ModuleProperty DamageModifier { get; } + 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); } @@ -141,12 +130,13 @@ private void DoDamageToPosition(Position location) { var distance = _weapon.ParentRobot.CurrentPosition.TotalDistance3D(location); var bulletTime = _weapon.GetAmmo().BulletTime; - var flyTime = (int) ((distance/bulletTime)*1000); + var flyTime = (int)((distance / bulletTime) * 1000); - var 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); + var damage = _weapon.GetDamageBuilder().Build().CalculatePlantDamages(); + if (damage <= 0.0) return; @@ -154,14 +144,14 @@ private void DoDamageToPosition(Position location) 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); } } } @@ -174,7 +164,7 @@ protected override void OnAction() protected virtual bool CheckAccuracy(Unit victim) { var rnd = FastRandom.NextDouble(); - var isMiss = rnd * _accuracy.Value > victim.SignatureRadius; + var isMiss = rnd * Accuracy.Value > victim.SignatureRadius; return isMiss; } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 7f663ac85..435ba8f05 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -253,6 +253,7 @@ + diff --git a/src/Perpetuum/Zones/Eggs/AreaBomb.cs b/src/Perpetuum/Zones/Eggs/AreaBomb.cs index 197642426..b0eab8162 100644 --- a/src/Perpetuum/Zones/Eggs/AreaBomb.cs +++ b/src/Perpetuum/Zones/Eggs/AreaBomb.cs @@ -45,6 +45,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/Terrains/TerrainExtensions.cs b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs index 5be74bc6f..6e8248b47 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs @@ -115,10 +115,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 +138,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 +155,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 +181,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; From 9321513016942a77f5a597649aaf715edbfbc1a8 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 Aug 2020 16:06:01 -0400 Subject: [PATCH 213/457] editting this file by hand is bad (#210) --- src/Perpetuum/Perpetuum.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 435ba8f05..7f663ac85 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -253,7 +253,6 @@ - From 14fe38105d9f5de46a3c914f96765dd55f0f1060 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 30 Aug 2020 13:50:50 -0400 Subject: [PATCH 214/457] Threat adding fix (#211) * fix adding threats for some neglected types * woops some test code didnt mean to commit * simplify checks * Pseudothreat refactor (#212) * refactor this cancer * spelling, refactors * revert some changes from interface changes * unused import * better performance with primitive lock * better filter linq logic on reward pseudothreat players --- .../EffectModules/SensorDampenerModule.cs | 2 +- .../Modules/EnergyNeutralizerModule.cs | 3 +- src/Perpetuum/Modules/EnergyVampireModule.cs | 2 +- src/Perpetuum/Modules/SensorJammerModule.cs | 4 +- src/Perpetuum/Zones/NpcSystem/Npc.cs | 118 +++++------------- .../Zones/NpcSystem/ThreatManager.cs | 117 ++++++++++++++++- 6 files changed, 151 insertions(+), 95 deletions(-) diff --git a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs index 26dd205b6..1e7670fa6 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -45,7 +45,7 @@ protected override bool CanApplyEffect(Unit target) 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) diff --git a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs index 73ff35716..72fd313e2 100644 --- a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs +++ b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs @@ -48,7 +48,8 @@ protected override void OnAction() 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); diff --git a/src/Perpetuum/Modules/EnergyVampireModule.cs b/src/Perpetuum/Modules/EnergyVampireModule.cs index 0519bc5e7..3c75cdf2f 100644 --- a/src/Perpetuum/Modules/EnergyVampireModule.cs +++ b/src/Perpetuum/Modules/EnergyVampireModule.cs @@ -57,7 +57,7 @@ protected override void OnAction() 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); diff --git a/src/Perpetuum/Modules/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index abbc4c1db..c0d1d898e 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -26,8 +26,6 @@ public override void AcceptVisitor(IEntityVisitor visitor) base.AcceptVisitor(visitor); } - private const double THREAT_SENSOR_JAMMER = 75.0; - protected override void OnAction() { var unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); @@ -40,7 +38,7 @@ protected override void OnAction() if (success) { robot.ResetLocks(); - robot.AddThreat(ParentRobot, new Threat(ThreatType.Undefined, THREAT_SENSOR_JAMMER)); + robot.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); } var packet = new CombatLogPacket(CombatLogType.Jammer, robot,ParentRobot,this); diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 591674281..7369c6a95 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -568,35 +568,7 @@ public AggressiveBehavior() : base(NpcBehaviorType.Aggressive) } } - public class PsuedoThreat - { - private TimeSpan _lastUpdated = TimeSpan.Zero; - private TimeSpan Expiration = TimeSpan.FromMinutes(1); - - public PsuedoThreat(Unit unit) - { - Unit = unit; - } - - public Unit Unit { get; } - - public bool IsExpired - { - get { return _lastUpdated > Expiration; } - } - - public void RefreshThreat() - { - _lastUpdated = TimeSpan.Zero; - } - - public void Update(TimeSpan time) - { - _lastUpdated += time; - } - } - - public class Npc : Creature,ITaggable + public class Npc : Creature, ITaggable { private readonly TagHelper _tagHelper; private const double CALL_FOR_HELP_ARMOR_THRESHOLD = 0.2; @@ -604,8 +576,7 @@ public class Npc : Creature,ITaggable private object _bestCombatRange; private TimeSpan _lastHelpCalled; private readonly EventListenerService _eventChannel; - private List PseudoThreats; - private object PseudoLock = new Object(); + private readonly IPseudoThreatManager _pseudoThreatManager; public Npc(TagHelper tagHelper, EventListenerService eventChannel) { @@ -613,7 +584,7 @@ public Npc(TagHelper tagHelper, EventListenerService eventChannel) _tagHelper = tagHelper; _threatManager = new ThreatManager(); AI = new StackFSM(); - PseudoThreats = new List(); + _pseudoThreatManager = new PseudoThreatManager(); } public NpcBehavior Behavior { get; set; } @@ -671,7 +642,7 @@ public Player GetTagger() return TagHelper.GetTagger(this); } - public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) + public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) { if (IsBoss() && hostile.IsPlayer()) { @@ -679,15 +650,7 @@ public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) } _threatManager.GetOrAddHostile(hostile).AddThreat(threat); - var PseudoThreat = PseudoThreats.Where(x => x.Unit == hostile).FirstOrDefault(); - if (PseudoThreat != null) - { - lock (PseudoLock) - { - PseudoThreats.Remove(PseudoThreat); - Logger.DebugInfo(" Removed " + PseudoThreat.ToString()); - } - } + RemovePseudoThreat(hostile); if (!spreadToGroup) return; @@ -708,19 +671,17 @@ public void AddThreat(Unit hostile, Threat threat,bool spreadToGroup) public void AddPseudoThreat(Unit hostile) { - lock (PseudoLock) - { - PseudoThreats.Add(new PsuedoThreat(hostile)); - } + _pseudoThreatManager.AddOrRefreshExisting(hostile); } private void UpdatePseudoThreats(TimeSpan time) { - lock (PseudoLock) - { - PseudoThreats.ForEach(threat => threat.Update(time)); - PseudoThreats.RemoveAll(threat => threat.IsExpired); - } + _pseudoThreatManager.Update(time); + } + + private void RemovePseudoThreat(Unit hostile) + { + _pseudoThreatManager.Remove(hostile); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -865,19 +826,16 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) if (ep > 0) { + var awardedPlayers = new List(); foreach (var hostile in ThreatManager.Hostiles) { - var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(hostile.unit); + var playerUnit = hostile.unit; + var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep); + awardedPlayers.Add(playerUnit); } - // same as above, but add half EP for players that were out of range of the NPC. - var psuedoHostiles = PseudoThreats.Where(p => !ThreatManager.Contains(p.Unit)); - foreach (var hostile in psuedoHostiles) - { - var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(hostile.Unit); - hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep / 2); - } + _pseudoThreatManager.AwardPseudoThreats(awardedPlayers, zone, ep); } scope.Complete(); @@ -1033,46 +991,30 @@ internal override bool IsHostile(AreaBomb bomb) return true; } - public bool CanAddThreatTo(Unit target,Threat threat) + /// + /// 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 aggressive and within aggrorange + /// - 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) { - var hasThreat = _threatManager.Contains(target); - if (hasThreat) + if (_threatManager.Contains(target)) 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; - } + return threat.type != ThreatType.Undefined; } - break; - } - case NpcBehaviorType.Aggressive: - { - check = true; - break; - } } - - return check && IsInAggroRange(target); + return IsInAggroRange(target); } private void AddBodyPullThreat(Unit enemy) diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs index 0f7433cbc..eb662ebe5 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; +using Perpetuum.Comparers; using Perpetuum.Timers; using Perpetuum.Units; @@ -16,7 +18,9 @@ public enum ThreatType Lock, Buff, Debuff, - Direct + Direct, + EnWar, + Ewar } public struct Threat @@ -186,4 +190,115 @@ public string ToDebugString() return sb.ToString(); } } + + /// + /// 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); + } + + + public class PseudoThreatManager : IPseudoThreatManager + { + private readonly List _pseudoThreats; + private readonly object _lock; + + public PseudoThreatManager() + { + _pseudoThreats = new List(); + _lock = new object(); + } + + 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); + } + } + + + /// + /// 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 PseudoThreat(Unit unit) + { + Unit = unit; + } + + public Unit Unit { get; } + + public bool IsExpired + { + get { return _lastUpdated > Expiration; } + } + + public void RefreshThreat() + { + _lastUpdated = TimeSpan.Zero; + } + + public void Update(TimeSpan time) + { + _lastUpdated += time; + } + } } From 456ff56ae70d5410485a81dc20cce04aa2558aa7 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 30 Aug 2020 13:54:11 -0400 Subject: [PATCH 215/457] Zone Effects (#193) * zone effects, reader, cached repo, single instance with concurrent dict, and handler, plus new zone effect entries, enumhelper * ide automation adding imports not very orderly * clean up, and log important stuff * add comparers folder back to proj file * bad merge? * duplicate entry --- .../PerpetuumBootstrapper.cs | 11 ++++- src/Perpetuum.ExportedTypes/EffectCategory.cs | 2 +- src/Perpetuum.ExportedTypes/EffectType.cs | 6 ++- src/Perpetuum/EnumHelper.cs | 33 +++++++++++++ src/Perpetuum/Perpetuum.csproj | 6 +++ src/Perpetuum/Players/Player.cs | 24 ++++++--- .../Effects/ZoneEffects/IZoneEffectHandler.cs | 12 +++++ .../ZoneEffects/IZoneEffectRepository.cs | 12 +++++ .../Zones/Effects/ZoneEffects/ZoneEffect.cs | 19 +++++++ .../Effects/ZoneEffects/ZoneEffectHandler.cs | 23 +++++++++ .../Effects/ZoneEffects/ZoneEffectReader.cs | 49 +++++++++++++++++++ .../ZoneEffects/ZoneEffectRepository.cs | 26 ++++++++++ .../Zones/SessionlessPlayerTimeout.cs | 2 + src/Perpetuum/Zones/Zone.cs | 1 + 14 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs create mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectRepository.cs create mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs create mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs create mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs create mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 5b3dd22a7..55948fa1d 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -121,6 +121,7 @@ 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; @@ -177,7 +178,6 @@ using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; - namespace Perpetuum.Bootstrapper { class EntityAggregateServices : IEntityServices @@ -648,6 +648,9 @@ private void RegisterEffects() { _builder.RegisterType(); + _builder.RegisterType().As().SingleInstance(); + _builder.RegisterType().As().SingleInstance(); + _builder.RegisterType().Keyed(EffectType.effect_invulnerable); _builder.RegisterType().Keyed(EffectType.effect_eccm); _builder.RegisterType().Keyed(EffectType.effect_stealth); @@ -718,7 +721,11 @@ private void RegisterEffects() _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl2); _builder.RegisterType().Keyed(EffectType.effect_pbs_booster_sensor_lvl3); - _builder.RegisterType().Keyed(EffectType.effect_beta_bonus); //TODO beta bonus + // 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 => { diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 2e97a1e57..f08f6da8a 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -52,6 +52,6 @@ public enum EffectCategory : long effcat_teleport_effects = 0x0000000008000000, effcat_terrain_object_effects = 0x0000080000000000, effcat_zero_core_drop = 0x0000000400000000, - effcat_zone_beta = 0x0000100000000000 //TODO beta bonus category -- might hijack other category if client cant display? + effcat_zone_effects = 0x0000100000000000 // New Zone effects } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 4ca641bb3..3b60a5665 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -102,6 +102,10 @@ public enum EffectType effect_teleport_self_enabler = 102, effect_teleport_sickness = 42, effect_thermal_resist = 13, - effect_beta_bonus = 103 //TODO beta bonus effect -- ID hardcoded from inserted DB id + // New effects - OPP + effect_beta_bonus = 103, + effect_beta2_bonus = 104, + effect_alpha_bonus = 105, + effect_alpha2_bonus = 106 } } 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 7f663ac85..26391fca9 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -612,6 +612,12 @@ + + + + + + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index ad9fb2d1f..4db6bf4d2 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -35,6 +35,7 @@ using Perpetuum.Zones.CombatLogs; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Effects; +using Perpetuum.Zones.Effects.ZoneEffects; using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Locking; @@ -136,9 +137,17 @@ public sealed class Player : Robot,IBlobableUnit,IBlobEmitter private readonly IBlobEmitter _blobEmitter; private readonly BlobHandler _blobHandler; private readonly PlayerMovement _movement; + private readonly IZoneEffectHandler _zoneEffectHandler; private CombatLogger _combatLogger; - public Player(IExtensionReader extensionReader,ICorporationManager corporationManager,MissionHandler.Factory missionHandlerFactory,ITeleportStrategyFactories teleportStrategyFactories,DockingBaseHelper dockingBaseHelper,CombatLogger.Factory combatLoggerFactory) + public Player(IExtensionReader extensionReader, + ICorporationManager corporationManager, + MissionHandler.Factory missionHandlerFactory, + ITeleportStrategyFactories teleportStrategyFactories, + DockingBaseHelper dockingBaseHelper, + CombatLogger.Factory combatLoggerFactory, + IZoneEffectHandler zoneEffectHandler + ) { _extensionReader = extensionReader; _corporationManager = corporationManager; @@ -146,6 +155,7 @@ public Player(IExtensionReader extensionReader,ICorporationManager corporationMa _teleportStrategyFactories = teleportStrategyFactories; _dockingBaseHelper = dockingBaseHelper; _combatLoggerFactory = combatLoggerFactory; + _zoneEffectHandler = zoneEffectHandler; Session = ZoneSession.None; _movement = new PlayerMovement(this); @@ -1039,13 +1049,13 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) } } - public void ApplyZoneEffects(IZone zone) + /// + /// Apply static zone-level effects to player when they enter zone + /// + /// Izone + private void ApplyZoneEffects(IZone zone) { - if (zone.Configuration.IsBeta) - { - var builder = NewEffectBuilder().SetType(EffectType.effect_beta_bonus).SetOwnerToSource(); - ApplyEffect(builder); - } + _zoneEffectHandler.ApplyZoneEffects(this, zone); } [CanBeNull] diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs new file mode 100644 index 000000000..6161c5a5c --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs @@ -0,0 +1,12 @@ +using Perpetuum.Players; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + /// + /// Handles application of any/all ZoneEffects + /// + public interface IZoneEffectHandler + { + void ApplyZoneEffects(Player player, IZone zone); + } +} 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..33048a2f8 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs @@ -0,0 +1,19 @@ +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + /// + /// An effect to be applied to everyone on a give zone + /// + public class ZoneEffect + { + public IZone Zone { get; private set; } + public EffectType Effect { get; private set; } + + public ZoneEffect(IZone zone, EffectType effectType) + { + Zone = zone; + Effect = effectType; + } + } +} diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs new file mode 100644 index 000000000..1ba9fdbb6 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs @@ -0,0 +1,23 @@ +using Perpetuum.Players; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + public class ZoneEffectHandler : IZoneEffectHandler + { + private readonly IZoneEffectRepository _zoneEffectRepository; + public ZoneEffectHandler(IZoneEffectRepository zoneEffectRepository) + { + _zoneEffectRepository = zoneEffectRepository; + } + + public void ApplyZoneEffects(Player player, IZone zone) + { + var zoneEffects = _zoneEffectRepository.GetZoneEffects(zone); + foreach (var zoneEffect in zoneEffects) + { + var builder = player.NewEffectBuilder().SetType(zoneEffect.Effect).SetOwnerToSource(); + player.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..c5be8f335 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs @@ -0,0 +1,49 @@ +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 class ZoneEffectReader + { + private readonly IZone _zone; + + public ZoneEffectReader(IZone zone) + { + _zone = zone; + } + + private ZoneEffect CreateZoneEffectFromRecord(IDataRecord record) + { + try + { + var value = record.GetValue("effectid"); + var effectType = EnumHelper.GetEnum(value); + var config = new ZoneEffect(_zone, effectType); + return config; + } + catch (Exception ex) + { + Logger.Exception(ex); + } + return null; + } + + public IEnumerable GetZoneEffects() + { + 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/Effects/ZoneEffects/ZoneEffectRepository.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs new file mode 100644 index 000000000..935e83972 --- /dev/null +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs @@ -0,0 +1,26 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.Effects.ZoneEffects +{ + public class ZoneEffectRepository : IZoneEffectRepository + { + private readonly ConcurrentDictionary> cache; + public ZoneEffectRepository() + { + cache = new ConcurrentDictionary>(); + } + + private List GetZoneEffectIfNotCached(IZone zone) + { + var zoneEffectReader = new ZoneEffectReader(zone); + return zoneEffectReader.GetZoneEffects().ToList(); + } + + public List GetZoneEffects(IZone zone) + { + return cache.GetOrAdd(zone.Id, () => GetZoneEffectIfNotCached(zone)); + } + } +} diff --git a/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs index b9acb7dde..e6dad788a 100644 --- a/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs +++ b/src/Perpetuum/Zones/SessionlessPlayerTimeout.cs @@ -1,5 +1,6 @@ using Perpetuum.Comparers; using Perpetuum.EntityFramework; +using Perpetuum.Log; using Perpetuum.Players; using Perpetuum.Timers; using System; @@ -90,6 +91,7 @@ public PlayerTimeout(Player p) public void RemoveFromZone() { + Logger.Warning($"Orphan Player removed from zone by timeout! {Player}"); Player.RemoveFromZone(); } diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 194f1322c..1901d7f6b 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -23,6 +23,7 @@ using Perpetuum.Zones.Beams; using Perpetuum.Zones.Blobs; using Perpetuum.Zones.Decors; +using Perpetuum.Zones.Effects; using Perpetuum.Zones.Environments; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; From c97d304e1acca3d2ec9f3c1e8647561146f4eae0 Mon Sep 17 00:00:00 2001 From: MikeJeffers <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 30 Aug 2020 13:54:37 -0400 Subject: [PATCH 216/457] Fix PBS death bug (#195) * Possibly resolve client crashes and server exceptions when destroying a gamma structure * better understanding of what is happening to cause the crashes. * very naive solution to understanding client fragility on pbs death * refactor zoneid cache value * undo some autoformatting * undo whitespace reformatting * rm ln * woops, flipped conditional Co-authored-by: Mark Rutherford --- .../Zones/PBS/DockingBases/PBSDockingBase.cs | 14 ++++++-------- src/Perpetuum/Zones/PBS/PBSHelper.cs | 9 +++++---- src/Perpetuum/Zones/PBS/PBSInterfaces.cs | 2 ++ src/Perpetuum/Zones/PBS/PBSObject.cs | 2 ++ src/Perpetuum/Zones/PBS/PBSObjectHelper.cs | 19 ++++++++++++++++--- .../Zones/PBS/PBSReinforceHandler.cs | 4 ++-- src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs | 5 +++++ 7 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index 95eb9c3f7..beb1699cd 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs @@ -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); @@ -148,9 +150,9 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) public override void OnLoadFromDb() { base.OnLoadFromDb(); - - _pbsObjectHelper.Init(); + _pbsObjectHelper.Init(); + _pbsTerritorialVisibilityHelper.Init(); } @@ -219,7 +221,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 +233,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 +306,6 @@ public Dictionary GetTerritorialDictionary() var ctd = GenerateTerritoryDictionary(); _cacheTerritoryDictionary = ctd; - } return _cacheTerritoryDictionary; @@ -483,7 +481,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/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 826b8ca69..70272ed9f 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -887,7 +887,7 @@ private static ErrorCodes HandleNodeDead(IZone zone, T pbsNode, Unit killer) return ec; } - public const int CONSTRUCTION_AMMO_DEFINITION = 4658; + public const int CONSTRUCTION_AMMO_DEFINITION = 4658; //TODO: Fetch this from DB public static IEnumerable GetConstructionAmmoLootOnDead(IPBSObject pbsObject) { @@ -1216,7 +1216,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; @@ -1264,8 +1264,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) @@ -1275,7 +1276,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; } diff --git a/src/Perpetuum/Zones/PBS/PBSInterfaces.cs b/src/Perpetuum/Zones/PBS/PBSInterfaces.cs index b5bff3fa5..7ba48681a 100644 --- a/src/Perpetuum/Zones/PBS/PBSInterfaces.cs +++ b/src/Perpetuum/Zones/PBS/PBSInterfaces.cs @@ -65,6 +65,8 @@ public interface IPBSObject void TakeOver(long newOwner); bool IsOrphaned { get; set; } + int ZoneIdCached { 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..02f25eb5c 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) + { + _pbsUnit.SendNodeUpdate(PBSEventType.nodeAttacked); + } } finally { @@ -454,7 +467,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..4af9f87f1 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); } From a24ef0390bc97462addda3c9a737c3bff8bc428d Mon Sep 17 00:00:00 2001 From: Doc-Saintly <43477526+Doc-Saintly@users.noreply.github.com> Date: Tue, 22 Sep 2020 16:25:20 -0700 Subject: [PATCH 217/457] Loyal zone draw map (#215) * WIP * adapt lambda to in loop operation * update slope and altitude correctly? * ints * ternary can be max, dont rampsample things into 0 * clean up a little * Revert SetValue override and add some notes about GetAltitude. Update Draw Ramp to use double all the way through and convert at the end. Remove rounding of altitude value. * left over from earlier inherit experiments Co-authored-by: Mark Rutherford Co-authored-by: mikejeffers Co-authored-by: docsaintly --- .../PerpetuumBootstrapper.cs | 1 + .../Perpetuum.RequestHandlers.csproj | 1 + .../Zone/ZoneDrawRamp.cs | 230 ++++++++++++++++++ src/Perpetuum/Argument.cs | 2 + src/Perpetuum/Commands.cs | 16 ++ src/Perpetuum/Zones/Terrains/AltitudeLayer.cs | 3 + 6 files changed, 253 insertions(+) create mode 100644 src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 55948fa1d..f44b9068f 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2744,6 +2744,7 @@ private void RegisterZoneRequestHandlers() RegisterZoneRequestHandler(Commands.PBSSetBaseDeconstruct); RegisterZoneRequestHandler(Commands.PBSSetReinforceOffset); RegisterZoneRequestHandler(Commands.PBSSetEffect); + RegisterZoneRequestHandler(Commands.ZoneDrawRamp); } diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 2a19893f3..dbd1d4293 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -590,6 +590,7 @@ + diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs new file mode 100644 index 000000000..603817db4 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs @@ -0,0 +1,230 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Host.Requests; +using Perpetuum.Players; +using Perpetuum.Zones; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.NpcSystem.Reinforcements; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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; + + 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/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/Commands.cs b/src/Perpetuum/Commands.cs index 8e3a25793..2a6738b0e 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -82,6 +82,22 @@ 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 ZoneDisplayMissionSpots = new Command { Text = "zoneDisplayMissionSpots", 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 From 98474ccb02b94645eebc77f98f17433a7d30a5ea Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 26 Sep 2020 15:59:50 -0400 Subject: [PATCH 218/457] Player movement validation (#217) * pos checking experiments with astar, bfs and others * raycast, more experiementing and refinements * some cleanup, 2 blocked tiles case on linear breaks basic tile skip case * unused imports * use em if ya store em * +1 * refactor to own file * refactor to player instead of zonesession * finally caught issue with the move predictor, all CurrentPosition to be set using this handler * overoptimization, the root of all success * queue and task for processing moves and resetting on bad pairs, bit messy, still debug and testing * mostly refactor * blockingcollection * cleanup some end condition checks, still working out safely disposing * use more useful method on blockingcollection * let the task get cancelled then eventfully dispose itself * static factory * extract disposing to owning class * safer constructor and use static factory to handle call order/setup * make sure session stops after movechecker in case of final packet send * unused import --- src/Perpetuum/PathFinders/AStarFinder.cs | 84 ++++++++++- src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Players/Player.cs | 42 ++++-- src/Perpetuum/Players/PlayerMoveChecker.cs | 162 +++++++++++++++++++++ src/Perpetuum/PointExtensions.cs | 1 - src/Perpetuum/Zones/ZoneExtensions.cs | 43 ++++++ src/Perpetuum/Zones/ZoneSession.cs | 3 +- 7 files changed, 315 insertions(+), 21 deletions(-) create mode 100644 src/Perpetuum/Players/PlayerMoveChecker.cs diff --git a/src/Perpetuum/PathFinders/AStarFinder.cs b/src/Perpetuum/PathFinders/AStarFinder.cs index 441449d42..729e07861 100644 --- a/src/Perpetuum/PathFinders/AStarFinder.cs +++ b/src/Perpetuum/PathFinders/AStarFinder.cs @@ -3,14 +3,85 @@ using System.Drawing; using System.Threading; using Perpetuum.Collections; +using Perpetuum.ExportedTypes; +using Perpetuum.Zones; namespace Perpetuum.PathFinders { + /// + /// 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 26391fca9..b7511cb0e 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -267,6 +267,7 @@ + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 4db6bf4d2..3646f4a72 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -69,7 +69,7 @@ public void Update(TimeSpan elapsed) elapsed = elapsed.Min(_maxElapsedTime); - var angle = _player.Direction * Math.PI * 2; + var angle = _player.Direction * MathHelper.PI2; var vx = Math.Sin(angle) * speed; var vy = Math.Cos(angle) * speed; @@ -88,26 +88,25 @@ public void Update(TimeSpan elapsed) 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); + var dx = (int)px - (int)nx; + var dy = (int)py - (int)ny; - if (dx >= 1.0 || dy >= 1.0) + if (dx != 0 || dy != 0) { // csak akkor kell ha csempevaltas volt if (!_player.IsWalkable((int)nx, (int)ny)) { - //_player.Zone.CreateAlignedDebugBeam(BeamType.red_20sec, new Position(nx, ny)); + //_player.Zone.CreateAlignedDebugBeam(BeamType.red_10sec, new Position(nx, ny)); break; } } px = nx; py = ny; - //_player.Zone.CreateAlignedDebugBeam(BeamType.blue_20sec, new Position(px, py)); + //_player.Zone.CreateAlignedDebugBeam(BeamType.blue_10sec, new Position(px, py)); } - - _player.CurrentPosition = new Position(px,py); - //_player.Zone.CreateAlignedDebugBeam(BeamType.orange_20sec, _player.CurrentPosition); + _player.TryMove(new Position(px, py)); + //_player.Zone.CreateAlignedDebugBeam(BeamType.orange_10sec, new Position(px, py)); } } @@ -139,6 +138,7 @@ public sealed class Player : Robot,IBlobableUnit,IBlobEmitter private readonly PlayerMovement _movement; private readonly IZoneEffectHandler _zoneEffectHandler; private CombatLogger _combatLogger; + private PlayerMoveCheckQueue _check; public Player(IExtensionReader extensionReader, ICorporationManager corporationManager, @@ -168,6 +168,17 @@ IZoneEffectHandler zoneEffectHandler public Character Character { get; set; } = Character.None; public bool HasGMStealth { get; set; } + public bool TryMove(Position position) + { + if (!IsWalkable(position)) + return false; + + _check.EnqueueMove(position); + + CurrentPosition = position; + return true; + } + public void SetSession(IZoneSession session) { Session = session; @@ -237,6 +248,7 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { base.OnEnterZone(zone, enterType); //aa + _check = PlayerMoveCheckQueue.Create(this, CurrentPosition); zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Visible, this)); @@ -258,8 +270,15 @@ protected override void OnRemovedFromZone(IZone zone) Session.SendPacket(ExitPacketBuilder); zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); - if (!States.LocalTeleport) - Session.Stop(); + Task.Run(() => + { + _check.Stop(); + _check.Dispose(); + }).ContinueWith(t => + { + if (!States.LocalTeleport) + Session.Stop(); + }); base.OnRemovedFromZone(zone); } @@ -1039,7 +1058,6 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) zone.SetGang(player); player.AddToZone(zone,validPosition,zoneEnterType); - player.ApplyInvulnerableEffect(); player.ApplyZoneEffects(zone); }); diff --git a/src/Perpetuum/Players/PlayerMoveChecker.cs b/src/Perpetuum/Players/PlayerMoveChecker.cs new file mode 100644 index 000000000..54f034af2 --- /dev/null +++ b/src/Perpetuum/Players/PlayerMoveChecker.cs @@ -0,0 +1,162 @@ +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 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 Stop() + { + if (!IsCanceled) + _tokenSrc.Cancel(); + + if (!IsCompleted) + _movesToReview.CompleteAdding(); + + _task.Wait(); + } + + 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/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/Zones/ZoneExtensions.cs b/src/Perpetuum/Zones/ZoneExtensions.cs index 5a6707f84..170cda40a 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.cs @@ -124,6 +124,49 @@ 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) + { + var x = start.X; + var y = start.Y; + var deltaX = Math.Abs(end.X - x); + var deltaY = Math.Abs(end.Y - y); + var travelDist = deltaX + deltaY; + var xIncrement = (end.X > x) ? 1 : -1; + var yIncrement = (end.Y > y) ? 1 : -1; + var error = deltaX - deltaY; + deltaX *= 2; + deltaY *= 2; + + for (var 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; diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index 810e2b618..10b8a7762 100644 --- a/src/Perpetuum/Zones/ZoneSession.cs +++ b/src/Perpetuum/Zones/ZoneSession.cs @@ -308,10 +308,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; } From a5de7f56f34e11a97b2358ccca5dc76d63df094d Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 13 Oct 2020 21:39:15 -0400 Subject: [PATCH 219/457] Island mask by command (#223) A command to apply proper blocking to tiles at or below water level --- .../Services/Channels/GameAdminCommands.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/Perpetuum/Services/Channels/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs index 91cc6e2ec..1ee0edd5a 100644 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/GameAdminCommands.cs @@ -565,6 +565,56 @@ public void ParseAdminCommand(Character sender, string text, IRequest request, C channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (PBSTerraformProtected)", lockedtiles.Count)); } + if (command[0] == "#zoneislandblock") + { + bool err = false; + var dictionary = new Dictionary(); + if (command.Length != 2) + { + channel.SendMessageToAll(sessionManager, sender, "Missing or too many args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + err = !int.TryParse(command[1], out int low); + if (err) + { + channel.SendMessageToAll(sessionManager, sender, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + dictionary.Add(k.low, low); + var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateIsland.Text, sender.ZoneId, GenxyConverter.Serialize(dictionary)); + channel.SendMessageToAll(sessionManager, sender, $"Islandblocking command accepted: {dictionary.ToDebugString()} \r\nBlocking... "); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + channel.SendMessageToAll(sessionManager, sender, $"Zone water level blocked! "); + return; + } + + if (command[0] == "#zonecreategarden") + { + bool err = false; + var dictionary = new Dictionary(); + if (command.Length != 3) + { + channel.SendMessageToAll(sessionManager, sender, "Missing or too many args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + err = !int.TryParse(command[1], out int x); + err = !int.TryParse(command[2], out int y); + if (err) + { + channel.SendMessageToAll(sessionManager, sender, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + dictionary.Add(k.x, x); + dictionary.Add(k.y, y); + var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateGarden.Text, sender.ZoneId, GenxyConverter.Serialize(dictionary)); + channel.SendMessageToAll(sessionManager, sender, $"Garden command accepted: {dictionary.ToDebugString()} \r\nPlanting... "); + request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); + channel.SendMessageToAll(sessionManager, sender, $"Garden Created! "); + return; + } + //MissionTestResolve - DEBUG ONLY if (command[0] == "#testmissions") { From bdea2aa8f99686e021f91a6da900687dc8c6267c Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 24 Oct 2020 15:48:27 -0400 Subject: [PATCH 220/457] Gameadmincommand major refactor (#225) --- .../PerpetuumBootstrapper.cs | 3 + src/Perpetuum/GlobalConfiguration.cs | 2 + src/Perpetuum/Perpetuum.csproj | 5 +- src/Perpetuum/Services/Channels/Channel.cs | 18 +- .../Services/Channels/ChannelManager.cs | 11 +- .../Channels/ChatCommands/AdminCommandData.cs | 43 + .../ChatCommands/AdminCommandHandlers.cs | 1006 ++++++++++++++++ .../ChatCommands/GameAdminCommands.cs | 81 ++ .../Services/Channels/GameAdminCommands.cs | 1022 ----------------- .../Zones/Terrains/TerrainControlInfo.cs | 2 +- 10 files changed, 1157 insertions(+), 1036 deletions(-) create mode 100644 src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs create mode 100644 src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs create mode 100644 src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs delete mode 100644 src/Perpetuum/Services/Channels/GameAdminCommands.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index f44b9068f..00cf9b3a6 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -78,6 +78,7 @@ using Perpetuum.Robots; using Perpetuum.Services; using Perpetuum.Services.Channels; +using Perpetuum.Services.Channels.ChatCommands; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Services.ExtensionService; @@ -535,6 +536,8 @@ private void InitContainer(string gameRoot) return configuration; }).SingleInstance(); + _builder.RegisterType().SingleInstance(); + _builder.RegisterType(); _builder.RegisterType().As(); _builder.RegisterType().As().SingleInstance(); diff --git a/src/Perpetuum/GlobalConfiguration.cs b/src/Perpetuum/GlobalConfiguration.cs index 7cb14150e..85d8a6665 100644 --- a/src/Perpetuum/GlobalConfiguration.cs +++ b/src/Perpetuum/GlobalConfiguration.cs @@ -18,6 +18,8 @@ public class GlobalConfiguration public string ResourceServerURL { get; set; } + public bool EnableDev { get; set; } + public CorporationConfiguration Corporation { get; set; } } } \ No newline at end of file diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index b7511cb0e..79b54ad9f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -285,7 +285,9 @@ - + + + @@ -1221,7 +1223,6 @@ - diff --git a/src/Perpetuum/Services/Channels/Channel.cs b/src/Perpetuum/Services/Channels/Channel.cs index cca52d9ac..ac494058b 100644 --- a/src/Perpetuum/Services/Channels/Channel.cs +++ b/src/Perpetuum/Services/Channels/Channel.cs @@ -9,13 +9,21 @@ public class Channel 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 GameAdminCommands AdminCommands = new GameAdminCommands(); - 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() { @@ -119,11 +127,11 @@ public void SetAdmin(bool isadminchannel) { if (isadminchannel) { - this.Type = ChannelType.Admin; + Type = ChannelType.Admin; } else { - this.Type = ChannelType.Public; + Type = _prevType; } } diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index fc379aa95..f32ff95ca 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -6,6 +6,7 @@ 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 @@ -18,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; @@ -28,6 +30,7 @@ public ChannelManager(ISessionManager sessionManager,IChannelRepository channelR _memberRepository = memberRepository; _banRepository = banRepository; _channelLoggerFactory = channelLoggerFactory; + _adminCommand = adminCommand; foreach (var channel in channelRepository.GetAll()) { @@ -264,12 +267,8 @@ public void Talk(string channelName, Character sender, string message, IRequest m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); channel.SendMessageToAll(_sessionManager, sender, message); - // this is an admin or GM command. - if (message.Substring(0, 1) == "#" && sender.AccessLevel == AccessLevel.admin) - { - channel.AdminCommands.ParseAdminCommand(sender, message, request, channel, _sessionManager, this); - } + _adminCommand.TryParseAdminCommand(sender, message, request, channel, this); } public void Announcement(string channelName, Character sender, string message) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs new file mode 100644 index 000000000..d2410f926 --- /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).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..a7c9f8cb0 --- /dev/null +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -0,0 +1,1006 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.GenXY; +using Perpetuum.Groups.Corporations; +using Perpetuum.Services.RiftSystem; +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); + + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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)); + } + 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; + int minutes = 1; + if (!int.TryParse(data.Command.Args[1], out 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("ShutdownCancel")] + public static void ShutdownCancel(AdminCommandData data) + { + string cmd = string.Format("serverShutDownCancel:relay:null"); + HandleLocalRequest(data, cmd); + } + [ChatCommand("JumpTo")] + public static void JumpTo(AdminCommandData data) + { + bool err = false; //TODO this only throws on bad last-arg! + err = !int.TryParse(data.Command.Args[0], out int zone); + err = !int.TryParse(data.Command.Args[1], out int x); + err = !int.TryParse(data.Command.Args[2], out int y); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + Dictionary dictionary = new Dictionary() + { + { "zoneID" , zone }, + { "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) + { + bool err = false; //TODO this parse checking is broken + err = !int.TryParse(data.Command.Args[0], out int characterID); + err = !int.TryParse(data.Command.Args[1], out int zoneID); + err = !int.TryParse(data.Command.Args[2], out int x); + err = !int.TryParse(data.Command.Args[3], out int y); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + // get the target character's session. + var 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. + var 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 + var 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); + tp = null; + + 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) + { + 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) + { + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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) + { + bool.TryParse(data.Command.Args[0], out bool visiblestate); + + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var 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) + { + //TODO check args, maybe allow zone id, fallback to sender zone + Dictionary dictionary = new Dictionary() + { + { "type", data.Command.Args[0] } + }; + + string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ListAllPlayersInZone")] + public static void ListAllPlayersInZone(AdminCommandData data) + { + int.TryParse(data.Command.Args[0], out int 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); + + var 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); + + var 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) + { + var 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) + { + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int characterID); + err = !bool.TryParse(data.Command.Args[1], out bool isoffensive); + + //TODO does this work if the character doesnt have a session? + var 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]; + + Corporation.IsNameOrNickTaken(desiredCorpName, desiredCorpNick).ThrowIfTrue(ErrorCodes.NameTaken); + var 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) + { + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int accountID); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + 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) + { + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int bonusBoost); + err = !int.TryParse(data.Command.Args[1], out int hours); + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + 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) + { + bool err = false; + + var character = data.Request.Session.Character; + IZone zone = null; + + if (data.Command.Args.Length == 1) + { + err = !int.TryParse(data.Command.Args[0], out int zoneCommand); + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + var zoneid = zoneCommand; + zone = data.Request.Session.ZoneMgr.GetZone((int)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); + } + + Dictionary dictionary = new Dictionary() + { + {"zoneid", zone.Id } + }; + if (zone.RelicManager != null) + { + var relicDictList = zone.RelicManager.GetRelicListDictionary(); + foreach (var dict in relicDictList) + { + SendMessageToAll(data, dict.ToDebugString()); + } + } + else + { + SendMessageToAll(data, "This zone does NOT support relics!"); + } + } + #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 = false; + err = !Int64.TryParse(data.Command.Args[0], out Int64 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; + + bool err = false; + err = !Int64.TryParse(data.Command.Args[0], out Int64 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; + + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int lvl); + + Dictionary dictionary = new Dictionary() + { + { "low", lvl } + }; + + string cmd = string.Format("zoneCreateIsland:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [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; + + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int definition); + err = !int.TryParse(data.Command.Args[1], out int x); + err = !int.TryParse(data.Command.Args[2], out int y); + err = !int.TryParse(data.Command.Args[3], out int z); + err = !double.TryParse(data.Command.Args[4], out double qx); + err = !double.TryParse(data.Command.Args[5], out double qy); + err = !double.TryParse(data.Command.Args[6], out double qz); + err = !double.TryParse(data.Command.Args[7], out double qw); + err = !double.TryParse(data.Command.Args[8], out double scale); + err = !int.TryParse(data.Command.Args[9], out int cat); + + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + 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 } + }; + + string cmd = string.Format("zoneDecorAdd:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneAddDecorToLockedTile")] + public static void ZoneAddDecorToLockedTile(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + return; + + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var terrainLock = player.GetPrimaryLock() as TerrainLock; + if (terrainLock == null) + { + return; + } + + double x = terrainLock.Location.X; + double y = terrainLock.Location.Y; + double z = terrainLock.Location.Z; + + bool err = !double.TryParse(data.Command.Args[1], out double scale); + err = !int.TryParse(data.Command.Args[0], out int definition); + + if (err) + { + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + 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; + + bool err = false; + err = !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; + + Dictionary dictionary = new Dictionary() + { + { "layerName", data.Command.Args[0] } + }; + + string cmd = string.Format("zoneClearLayer:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneSetPlantSpeed")] + public static void ZoneSetPlantSpeed(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + return; + + bool err = false; + err = !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)) + 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; + + Dictionary dictionary = 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; + + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int def); + int[] defs = new int[1]; + defs[0] = def; + + Dictionary dictionary = new Dictionary() + { + { "definition", defs } + }; + + string cmd = string.Format("zoneDrawBlockingByDefinition:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + HandleLocalRequest(data, cmd); + } + [ChatCommand("ZoneAddBlockingToLockedTiles")] + public static void ZoneAddBlockingToLockedTiles(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + return; + + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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; + + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var 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; + + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int id); + err = !int.TryParse(data.Command.Args[1], out int locked); + + 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; + + bool err = false; + var dictionary = new Dictionary(); + if (data.Command.Args.Length == 1) + { + 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); + } + var 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; + + bool err = false; + var dictionary = new Dictionary(); + if (data.Command.Args.Length != 1) + { + SendMessageToAll(data, "Missing or too many args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + 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); + var 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; + + bool err = false; + var dictionary = new Dictionary(); + if (data.Command.Args.Length != 2) + { + SendMessageToAll(data, "Missing or too many args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + err = !int.TryParse(data.Command.Args[0], out int x); + err = !int.TryParse(data.Command.Args[1], out int y); + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + dictionary.Add(k.x, x); + dictionary.Add(k.y, y); + var 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("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; + + bool err = false; + var character = data.Request.Session.Character; + var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + var player = zone.GetPlayer(character.ActiveRobotEid); + + var terrainLock = player.GetPrimaryLock() as TerrainLock; + + int x, y, zoneid; + + if (terrainLock == null) + { + if (data.Command.Args.Length != 3) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + err = !int.TryParse(data.Command.Args[0], out int xCommand); + err = !int.TryParse(data.Command.Args[1], out int yCommand); + err = !int.TryParse(data.Command.Args[2], out int zoneCommand); + if (err) + { + SendMessageToAll(data, "Bad args"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + x = xCommand; + y = yCommand; + zoneid = zoneCommand; + zone = data.Request.Session.ZoneMgr.GetZone((int)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..fedf73b90 --- /dev/null +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -0,0 +1,81 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Host.Requests; +using Perpetuum.Services.Sessions; +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) + { + if (IsAdminCommand(sender, text)) + { + ParseAdminCommand(sender, text, request, channel, channelManager); + } + } + + private bool IsAdminCommand(Character sender, string message) + { + return message.StartsWith("#") && sender.AccessLevel == AccessLevel.admin; + } + + private void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) + { + if (!IsAdminCommand(sender, text)) + return; + + 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") + { + 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/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/GameAdminCommands.cs deleted file mode 100644 index 1ee0edd5a..000000000 --- a/src/Perpetuum/Services/Channels/GameAdminCommands.cs +++ /dev/null @@ -1,1022 +0,0 @@ -using Perpetuum.Accounting.Characters; -using Perpetuum.Groups.Corporations; -using Perpetuum.ExportedTypes; -using Perpetuum.GenXY; -using Perpetuum.Host.Requests; -using Perpetuum.Players; -using Perpetuum.Services.Mail; -using Perpetuum.Services.RiftSystem; -using Perpetuum.Services.Sessions; -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; -using System.Text; -using System.Threading.Tasks; - -namespace Perpetuum.Services.Channels -{ - public class GameAdminCommands - { - public GameAdminCommands() - { - - } - - // obviously everything coming in from the in-game chat is a string. - // we have to take that string and chop it up, work out what command is being executed - // then parse/cast/convert arguments as necessary. - public void ParseAdminCommand(Character sender, string text, IRequest request, Channel channel, ISessionManager sessionManager, ChannelManager channelmanager) - { - string[] command = text.Split(new char[] { ',' }); - - // channel is not secured. must be secured first. - if (channel.Type != ChannelType.Admin) - { - - if (command[0] == "#secure") - { - channel.SetAdmin(true); - channel.SendMessageToAll(sessionManager, sender, "Channel Secured."); - return; - } - - channel.SendMessageToAll(sessionManager, sender, "Channel must be secured before sending commands."); - return; - } - - if (command[0] == "#shutdown") - { - DateTime shutdownin = DateTime.Now; - int minutes = 1; - if (!int.TryParse(command[2], out minutes)) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - shutdownin = shutdownin.AddMinutes(minutes); - - Dictionary dictionary = new Dictionary() - { - { "message", command[1] }, - { "date", shutdownin } - }; - - string cmd = string.Format("serverShutDown:relay:{0}", GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#shutdowncancel") - { - string cmd = string.Format("serverShutDownCancel:relay:null"); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#jumpto") - { - bool err = false; - err = !int.TryParse(command[1], out int zone); - err = !int.TryParse(command[2], out int x); - err = !int.TryParse(command[3], out int y); - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - Dictionary dictionary = new Dictionary() - { - { "zoneID" , zone }, - { "x" , x }, - { "y" , y } - }; - - string cmd = string.Format("jumpAnywhere:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#moveplayer") - { - bool err = false; - err = !int.TryParse(command[1], out int characterID); - err = !int.TryParse(command[2], out int zoneID); - err = !int.TryParse(command[3], out int x); - err = !int.TryParse(command[4], out int y); - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - // get the target character's session. - var charactersession = sessionManager.GetByCharacter(characterID); - - if (charactersession.Character.ZoneId == null) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("ERR: Character with ID {0} does not have a zone. Are they docked?", characterID)); - return; - } - - // get destination zone. - var zone = request.Session.ZoneMgr.GetZone(zoneID); - - if (charactersession.Character.ZoneId == null) - { - channel.SendMessageToAll(sessionManager, sender, 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 - var player = 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); - tp = null; - - channel.SendMessageToAll(sessionManager, sender, string.Format("Moved Character {0}-{1} to Zone {2} @ {3},{4}", characterID, charactersession.Character.Nick, zone.Id, x, y)); - } -#if DEBUG - if (command[0] == "#currentzonecleanobstacleblocking") - { - string cmd = string.Format("zoneCleanObstacleBlocking:zone_{0}:null", sender.ZoneId); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#currentzonedrawblockingbyeid") - { - bool err = false; - err = !Int64.TryParse(command[1], out Int64 eid); - - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - Dictionary dictionary = new Dictionary() - { - { "eid", eid } - }; - - string cmd = string.Format("zoneDrawBlockingByEid:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#currentzoneremoveobjectbyeid") - { - bool err = false; - err = !Int64.TryParse(command[1], out Int64 eid); - - Dictionary dictionary = new Dictionary() - { - { "target", eid } - }; - - string cmd = string.Format("zoneRemoveObject:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#zonecreateisland") - { - bool err = false; - err = !int.TryParse(command[1], out int lvl); - - Dictionary dictionary = new Dictionary() - { - { "low", lvl } - }; - - string cmd = string.Format("zoneCreateIsland:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#currentzoneplacewall") - { - string cmd = string.Format("zonePlaceWall:zone_{0}:null", sender.ZoneId); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#currentzoneclearwalls") - { - string cmd = string.Format("zoneClearWalls:zone_{0}:null", sender.ZoneId); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#currentzoneadddecor") - { - bool err = false; - err = !int.TryParse(command[1], out int definition); - err = !int.TryParse(command[2], out int x); - err = !int.TryParse(command[3], out int y); - err = !int.TryParse(command[4], out int z); - err = !double.TryParse(command[5], out double qx); - err = !double.TryParse(command[6], out double qy); - err = !double.TryParse(command[7], out double qz); - err = !double.TryParse(command[8], out double qw); - err = !double.TryParse(command[9], out double scale); - err = !int.TryParse(command[10], out int cat); - - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - 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 } - }; - - string cmd = string.Format("zoneDecorAdd:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#adddecortolockedtile") - { - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var terrainLock = player.GetPrimaryLock() as TerrainLock; - if (terrainLock == null) - { - return; - } - - double x = terrainLock.Location.X; - double y = terrainLock.Location.Y; - double z = terrainLock.Location.Z; - - bool err = !double.TryParse(command[2], out double scale); - err = !int.TryParse(command[1], out int definition); - - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - 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}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#zonedeletedecor") - { - bool err = false; - err = !int.TryParse(command[1], out int idno); - - Dictionary dictionary = new Dictionary() - { - { "ID", idno } - }; - - string cmd = string.Format("zoneDecorDelete:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#zoneclearlayer") - { - - Dictionary dictionary = new Dictionary() - { - { "layerName", command[1] } - }; - - string cmd = string.Format("zoneClearLayer:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#zonesetplantspeed") - { - bool err = false; - err = !int.TryParse(command[1], out int speed); - - Dictionary dictionary = new Dictionary() - { - { "speed", speed } - }; - - string cmd = string.Format("zoneSetPlantsSpeed:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#zonesetplantmode") - { - Dictionary dictionary = new Dictionary() - { - { "mode", command[1] } - }; - - string cmd = string.Format("zoneSetPlantsMode:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#currentzonerestoreoriginalgamma") - { - string cmd = string.Format("zoneRestoreOriginalGamma:zone_{0}:null", sender.ZoneId); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#zonedrawblockingbydefinition") - { - bool err = false; - err = !int.TryParse(command[1], out int def); - int[] defs = new int[1]; - defs[0] = def; - - Dictionary dictionary = new Dictionary() - { - { "definition", defs } - }; - - string cmd = string.Format("zoneDrawBlockingByDefinition:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - if (command[0] == "#addblockingtotiles") - { - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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. - } - } - - channel.SendMessageToAll(sessionManager, sender, string.Format("Added Blocking To {0} Tiles.", lockedtiles.Count)); - } - - if (command[0] == "#removeblockingfromtiles") - { - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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. - } - } - - channel.SendMessageToAll(sessionManager, sender, string.Format("Removed Blocking From {0} Tiles.", lockedtiles.Count)); - } - - if (command[0] == "#zonedecorlock") - { - - bool err = false; - err = !int.TryParse(command[1], out int id); - err = !int.TryParse(command[2], out int locked); - - Dictionary dictionary = new Dictionary() - { - { "ID", id }, - { "locked", locked } - }; - - string cmd = string.Format("zoneDecorLock:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - - } - - if (command[0] == "#zonetileshighway") - { - bool.TryParse(command[1], out bool adddelete); - bool.TryParse(command[2], out bool keeplock); - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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.Highway = adddelete; - zone.Terrain.Controls.SetValue(pos, ti); - if (!keeplock) - { - item.Cancel(); // cancel this lock. we processed it. - } - } - } - channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (Highway)", lockedtiles.Count)); - } - - if (command[0] == "#zonetilesconcretea") - { - bool.TryParse(command[1], out bool adddelete); - bool.TryParse(command[2], out bool keeplock); - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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.ConcreteA = adddelete; - zone.Terrain.Controls.SetValue(pos, ti); - if (!keeplock) - { - item.Cancel(); // cancel this lock. we processed it. - } - } - } - channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (ConcreteA)", lockedtiles.Count)); - } - - if (command[0] == "#zonetilesconcreteb") - { - - bool.TryParse(command[1], out bool adddelete); - bool.TryParse(command[2], out bool keeplock); - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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.ConcreteB = adddelete; - zone.Terrain.Controls.SetValue(pos, ti); - if (!keeplock) - { - item.Cancel(); // cancel this lock. we processed it. - } - } - } - channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (ConcreteB)", lockedtiles.Count)); - } - - if (command[0] == "#zonetilesroaming") - { - - bool.TryParse(command[1], out bool adddelete); - bool.TryParse(command[2], out bool keeplock); - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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.Roaming = adddelete; - zone.Terrain.Controls.SetValue(pos, ti); - if (!keeplock) - { - item.Cancel(); // cancel this lock. we processed it. - } - } - } - channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (Roaming)", lockedtiles.Count)); - } - - if (command[0] == "#zonetilesPBSTerraformProtected") - { - - bool.TryParse(command[1], out bool adddelete); - bool.TryParse(command[2], out bool keeplock); - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var 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.PBSTerraformProtected = adddelete; - zone.Terrain.Controls.SetValue(pos, ti); - if (!keeplock) - { - item.Cancel(); // cancel this lock. we processed it. - } - } - } - channel.SendMessageToAll(sessionManager, sender, string.Format("Altered state of control layer on {0} Tiles (PBSTerraformProtected)", lockedtiles.Count)); - } - - if (command[0] == "#zoneislandblock") - { - bool err = false; - var dictionary = new Dictionary(); - if (command.Length != 2) - { - channel.SendMessageToAll(sessionManager, sender, "Missing or too many args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - err = !int.TryParse(command[1], out int low); - if (err) - { - channel.SendMessageToAll(sessionManager, sender, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - dictionary.Add(k.low, low); - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateIsland.Text, sender.ZoneId, GenxyConverter.Serialize(dictionary)); - channel.SendMessageToAll(sessionManager, sender, $"Islandblocking command accepted: {dictionary.ToDebugString()} \r\nBlocking... "); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - channel.SendMessageToAll(sessionManager, sender, $"Zone water level blocked! "); - return; - } - - if (command[0] == "#zonecreategarden") - { - bool err = false; - var dictionary = new Dictionary(); - if (command.Length != 3) - { - channel.SendMessageToAll(sessionManager, sender, "Missing or too many args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - err = !int.TryParse(command[1], out int x); - err = !int.TryParse(command[2], out int y); - if (err) - { - channel.SendMessageToAll(sessionManager, sender, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - dictionary.Add(k.x, x); - dictionary.Add(k.y, y); - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateGarden.Text, sender.ZoneId, GenxyConverter.Serialize(dictionary)); - channel.SendMessageToAll(sessionManager, sender, $"Garden command accepted: {dictionary.ToDebugString()} \r\nPlanting... "); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - channel.SendMessageToAll(sessionManager, sender, $"Garden Created! "); - return; - } - - //MissionTestResolve - DEBUG ONLY - if (command[0] == "#testmissions") - { - int.TryParse(command[1], out int charID); - int.TryParse(command[2], out int zoneID); - int.TryParse(command[3], out int level); - int.TryParse(command[4], out int numAttempts); - int.TryParse(command[5], out int displayFlag); - int.TryParse(command[6], 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)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Running missionresolve test {0}", dictionary.ToDebugString())); - } - -#endif - - if (command[0] == "#giveitem") - { - - int.TryParse(command[1], out int definition); - int.TryParse(command[2], out int qty); - - Dictionary dictionary = new Dictionary() - { - { "definition", definition }, - { "quantity", qty } - }; - - - string cmd = string.Format("createItem:relay:{0}", GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Gave Item {0} ", definition)); - } - - - if (command[0] == "#getlockedtileproperties") - { - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var lockedtile = player.GetPrimaryLock(); - - TerrainControlInfo ti = zone.Terrain.Controls.GetValue((lockedtile as TerrainLock).Location); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Tile at {0},{1} has the following flags..", (lockedtile as TerrainLock).Location.X, (lockedtile as TerrainLock).Location.Y)); - channel.SendMessageToAll(sessionManager, sender, "TerrainControlFlags:"); - foreach (TerrainControlFlags f in Enum.GetValues(typeof(TerrainControlFlags))) - { - if (ti.Flags.HasFlag(f) && f != TerrainControlFlags.Undefined) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); - } - } - - BlockingInfo bi = zone.Terrain.Blocks.GetValue((lockedtile as TerrainLock).Location); - - channel.SendMessageToAll(sessionManager, sender, "BlockingFlags:"); - foreach (BlockingFlags f in Enum.GetValues(typeof(BlockingFlags))) - { - if (bi.Flags.HasFlag(f) && f != BlockingFlags.Undefined) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); - } - } - - PlantInfo pi = zone.Terrain.Plants.GetValue((lockedtile as TerrainLock).Location); - - channel.SendMessageToAll(sessionManager, sender, "PlantType:"); - foreach (PlantType f in Enum.GetValues(typeof(PlantType))) - { - if (pi.type.HasFlag(f) && f != PlantType.NotDefined) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); - } - } - - channel.SendMessageToAll(sessionManager, sender, "GroundType:"); - foreach (GroundType f in Enum.GetValues(typeof(GroundType))) - { - if (pi.groundType.HasFlag(f)) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("{0}", f.ToString())); - } - } - - } - - if (command[0] == "#setvisibility") - { - - bool.TryParse(command[1], out bool visiblestate); - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - player.HasGMStealth = !visiblestate; - - channel.SendMessageToAll(sessionManager, sender, string.Format("Player {0} visibility is {1}", player.Character.Nick, visiblestate)); - } - - if (command[0] == "#zonedrawstatmap") - { - - Dictionary dictionary = new Dictionary() - { - { "type", command[1] } - }; - - string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", sender.ZoneId, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - } - - - if (command[0] == "#listplayersinzone") - { - - int.TryParse(command[1], out int zoneid); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}", zoneid)); - channel.SendMessageToAll(sessionManager, sender, string.Format(" AccountId CharacterId Nick Access Level Docked? DockedAt Position")); - foreach (Character c in sessionManager.SelectedCharacters.Where(x => x.ZoneId == zoneid)) - { - channel.SendMessageToAll(sessionManager, sender, 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)); - } - } - - if (command[0] == "#countofplayers") - { - foreach (IZone z in request.Session.ZoneMgr.Zones) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("Players On Zone {0}: {1}", z.Id, z.Players.ToList().Count)); - } - } - - if (command[0] == "#unsecure") - { - channel.SetAdmin(false); - channel.SendMessageToAll(sessionManager, sender, "Channel is now public."); - } - - if (command[0] == "#addtochannel") - { - int.TryParse(command[1], out int characterid); - - var c = sessionManager.GetByCharacter(characterid); - - channelmanager.JoinChannel(channel.Name, c.Character, ChannelMemberRole.Operator, string.Empty); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Added character {0} to channel ", c.Character.Nick)); - - } - - if (command[0] == "#removefromchannel") - { - int.TryParse(command[1], out int characterid); - - var c = sessionManager.GetByCharacter(characterid); - - channelmanager.LeaveChannel(channel.Name, c.Character); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Removed character {0} from channel ", c.Character.Nick)); - - } - - if (command[0] == "#listrifts") - { - foreach (IZone z in request.Session.ZoneMgr.Zones) - { - var rift = z.Units.OfType(); - foreach (Rift r in rift) - { - channel.SendMessageToAll(sessionManager, sender, string.Format("Rift - Zone: {0}, Position: ({1})", r.Zone, r.CurrentPosition)); - } - } - } - - - if (command[0] == "#flagplayernameoffensive") - { - bool err = false; - err = !int.TryParse(command[1], out int characterID); - err = !bool.TryParse(command[2], out bool isoffensive); - - var charactersession = sessionManager.GetByCharacter(characterID); - charactersession.Character.IsOffensiveNick = isoffensive; - - channel.SendMessageToAll(sessionManager, sender, string.Format("Player with nick {0} is offensive:{1}", charactersession.Character.Nick, charactersession.Character.IsOffensiveNick)); - - } - - if (command[0] == "#renamecorp") - { - string currentCorpName = command[1]; - string desiredCorpName = command[2]; - string desiredCorpNick = command[3]; - - Corporation.IsNameOrNickTaken(desiredCorpName, desiredCorpNick).ThrowIfTrue(ErrorCodes.NameTaken); - var corp = Corporation.GetByName(currentCorpName); - corp.SetName(desiredCorpName, desiredCorpNick); - - channel.SendMessageToAll(sessionManager, sender, string.Format("Corp with nick {0} changed to: {1} [{2}]", currentCorpName, desiredCorpName, desiredCorpNick)); - } - - - //FreeAllLockedEP for account - by request of player - if (command[0] == "#unlockallep") - { - bool err = false; - err = !int.TryParse(command[1], out int accountID); - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - Dictionary dictionary = new Dictionary() - { - { k.accountID, accountID } - }; - - string cmd = string.Format("{0}:relay:{1}", Commands.ExtensionFreeAllLockedEpCommand.Text, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - channel.SendMessageToAll(sessionManager, sender, "unlockallep: " + dictionary.ToDebugString()); - } - - //EPBonusCommands - if (command[0] == "#epbonusset") - { - bool err = false; - err = !int.TryParse(command[1], out int bonusBoost); - err = !int.TryParse(command[2], out int hours); - if (err) - { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - Dictionary dictionary = new Dictionary() - { - { k.bonus, bonusBoost }, - { k.duration, hours } - }; - - string cmd = string.Format("{0}:relay:{1}", Commands.EPBonusSet.Text, GenxyConverter.Serialize(dictionary)); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - channel.SendMessageToAll(sessionManager, sender, "EP Bonus Set with command: " + dictionary.ToDebugString()); - } - - -#if DEBUG - //SpawnRelics by command - if (command[0] == "#spawnrelic") - { - bool err = false; - - var character = request.Session.Character; - var zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); - - var terrainLock = player.GetPrimaryLock() as TerrainLock; - - int x, y, zoneid; - - if (terrainLock == null) - { - if (command.Length != 4) - { - channel.SendMessageToAll(sessionManager, sender, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - err = !int.TryParse(command[1], out int xCommand); - err = !int.TryParse(command[2], out int yCommand); - err = !int.TryParse(command[3], out int zoneCommand); - if (err) - { - channel.SendMessageToAll(sessionManager, sender, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - x = xCommand; - y = yCommand; - zoneid = zoneCommand; - zone = request.Session.ZoneMgr.GetZone((int)zoneid); - if (zone == null) - { - channel.SendMessageToAll(sessionManager, sender, "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) - { - channel.SendMessageToAll(sessionManager, sender, "Spawned relic at: " + dictionary.ToDebugString()); - } - else - { - channel.SendMessageToAll(sessionManager, sender, "FAILED to spawn relic at: " + dictionary.ToDebugString()); - } - } - else - { - channel.SendMessageToAll(sessionManager, sender, "This zone does NOT support relics!"); - } - - } -#endif - - //List all Relics - if (command[0] == "#listrelics") - { - bool err = false; - - var character = request.Session.Character; - IZone zone = null; - - if (command.Length == 2) - { - err = !int.TryParse(command[1], out int zoneCommand); - if (err) - { - channel.SendMessageToAll(sessionManager, sender, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - var zoneid = zoneCommand; - zone = request.Session.ZoneMgr.GetZone((int)zoneid); - } - else if (character.ZoneId != null) - { - zone = request.Session.ZoneMgr.GetZone((int)character.ZoneId); - } - - if (zone == null) - { - channel.SendMessageToAll(sessionManager, sender, "Zone not provided or not found"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - - Dictionary dictionary = new Dictionary() - { - {"zoneid", zone.Id } - }; - if (zone.RelicManager != null) - { - var relicDictList = zone.RelicManager.GetRelicListDictionary(); - foreach (var dict in relicDictList) - { - channel.SendMessageToAll(sessionManager, sender, dict.ToDebugString()); - } - } - else - { - channel.SendMessageToAll(sessionManager, sender, "This zone does NOT support relics!"); - } - - } - - if (command[0] == "#savelayers") - { - bool err = false; - var dictionary = new Dictionary(); - if (command.Length == 2) - { - err = !int.TryParse(command[1], out int zoneId); - if (err) - { - channel.SendMessageToAll(sessionManager, sender, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - dictionary.Add(k.zoneID, zoneId); - } - var cmd = string.Format("{0}:relay:{1}", Commands.ZoneSaveLayer.Text, GenxyConverter.Serialize(dictionary)); - channel.SendMessageToAll(sessionManager, sender, $"SaveLayers command accepted: {dictionary.ToDebugString()} \r\nSaving... "); - request.Session.HandleLocalRequest(request.Session.CreateLocalRequest(cmd)); - channel.SendMessageToAll(sessionManager, sender, $"Layer(s) Saved! "); - } - } - } -} 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)); } From c998f8bd63651c7001b6a917c7af54dd561ccde2 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 24 Oct 2020 16:19:36 -0400 Subject: [PATCH 221/457] Weather fix (#226) * fix weather observer, and add a command for testing fun --- .../ChatCommands/AdminCommandHandlers.cs | 44 +++++++++++++++++++ src/Perpetuum/Zones/IWeatherService.cs | 1 + src/Perpetuum/Zones/WeatherInfo.cs | 9 ++++ src/Perpetuum/Zones/WeatherService.cs | 14 +++++- src/Perpetuum/Zones/ZoneSession.cs | 26 +++++++++-- 5 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index a7c9f8cb0..c7d6cee6d 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -442,6 +442,50 @@ public static void ListRelics(AdminCommandData data) 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); + } + var zoneId = data.Sender.ZoneId ?? -1; + bool err = false; + err = !int.TryParse(data.Command.Args[0], out int weatherInt); + err = !int.TryParse(data.Command.Args[1], out int seconds); + if (data.Command.Args.Length > 2) + { + err = !int.TryParse(data.Command.Args[2], out zoneId); + } + if (!data.Request.Session.ZoneMgr.ContainsZone(zoneId)) + { + SendMessageToAll(data, $"Bad or missing zone id"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + var current = zone.Weather.GetCurrentWeather(); + var weather = new WeatherInfo(current.Next, weatherInt.Min(255), TimeSpan.FromSeconds(seconds)); + zone.Weather.SetCurrentWeather(weather); + SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather().ToString()}"); + } + [ChatCommand("GetWeather")] + public static void GetWeather(AdminCommandData data) + { + var zoneId = data.Sender.ZoneId ?? -1; + bool err = false; + if (!data.Command.Args.IsNullOrEmpty()) + { + err = !int.TryParse(data.Command.Args[0], out zoneId); + } + if (!data.Request.Session.ZoneMgr.ContainsZone(zoneId) || err) + { + SendMessageToAll(data, $"Bad or missing zone id"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather().ToString()}"); + } #endregion #region DevCommands [ChatCommand("ZoneCleanObstacleBlocking")] 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/WeatherInfo.cs b/src/Perpetuum/Zones/WeatherInfo.cs index 669d33776..ff1c75873 100644 --- a/src/Perpetuum/Zones/WeatherInfo.cs +++ b/src/Perpetuum/Zones/WeatherInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Perpetuum.Timers; namespace Perpetuum.Zones @@ -33,5 +34,13 @@ public Packet CreateUpdatePacket() 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(); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/WeatherService.cs b/src/Perpetuum/Zones/WeatherService.cs index 3b67df44d..3854d3638 100644 --- a/src/Perpetuum/Zones/WeatherService.cs +++ b/src/Perpetuum/Zones/WeatherService.cs @@ -53,8 +53,7 @@ public override void Update(TimeSpan time) return; _current = GetNextWeather(); - var packet = _current.CreateUpdatePacket(); - _observable.OnNext(packet); + SendWeatherUpdate(_current); } public IDisposable Subscribe(IObserver observer) @@ -66,5 +65,16 @@ public WeatherInfo GetCurrentWeather() { return _current; } + + private void SendWeatherUpdate(WeatherInfo info) + { + _observable.OnNext(info.CreateUpdatePacket()); + } + + public void SetCurrentWeather(WeatherInfo weather) + { + _current = weather; + SendWeatherUpdate(_current); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index 10b8a7762..6b66a98fc 100644 --- a/src/Perpetuum/Zones/ZoneSession.cs +++ b/src/Perpetuum/Zones/ZoneSession.cs @@ -245,7 +245,7 @@ private void LogGenxyException(Packet packet, PerpetuumException gex) } private BeamsMonitor _beamsMonitor; - private Observer _weatherMonitor; + private WeatherMonitor _weatherMonitor; private void HandleAuth(Packet packet) { @@ -271,8 +271,8 @@ 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); @@ -928,6 +928,26 @@ public void Update(TimeSpan time) UpdateLogout(time); } + private class WeatherMonitor: Observer + { + private Action _onNext; + public WeatherMonitor(Action onNext) + { + _onNext = onNext; + } + + public override void OnNext(Packet packet) + { + _onNext(packet); + } + + protected override void OnDispose() + { + _onNext = null; + base.OnDispose(); + } + } + private class BeamsMonitor : Observer { private readonly ZoneSession _session; From a6072e7d23d2f12162f72392493591cbf589d106 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 26 Oct 2020 20:02:46 -0400 Subject: [PATCH 222/457] Environmental Effects: make zone effects dynamic (#227) --- .../PerpetuumBootstrapper.cs | 37 ++++++++- src/Perpetuum.ExportedTypes/EffectCategory.cs | 4 +- src/Perpetuum.ExportedTypes/EffectType.cs | 6 +- src/Perpetuum/Perpetuum.csproj | 17 ++-- src/Perpetuum/Players/Player.cs | 15 +--- .../ChatCommands/AdminCommandHandlers.cs | 1 + .../EventMessages/GameTimeMessage.cs | 13 +++ .../EventMessages/WeatherEventMessage.cs | 15 ++++ .../EventProcessors/GameTimeEventProcessor.cs | 60 ++++++++++++++ .../EventProcessors/WeatherWatcher.cs | 58 +++++++++++++ .../Services/GameTime/GameTimeInfo.cs | 52 ++++++++++++ .../Services/GameTime/GameTimeObserver.cs | 23 ++++++ .../Services/GameTime/GameTimeService.cs | 52 ++++++++++++ .../Services/GameTime/IGameTimeService.cs | 14 ++++ .../Services/Weather/IWeatherService.cs | 15 ++++ .../Weather}/WeatherInfo.cs | 34 ++++++-- .../Services/Weather/WeatherObservers.cs | 44 ++++++++++ .../Weather}/WeatherService.cs | 16 ++-- .../Effects/ZoneEffects/IZoneEffectHandler.cs | 20 ++++- .../Zones/Effects/ZoneEffects/ZoneEffect.cs | 36 ++++++++- .../Effects/ZoneEffects/ZoneEffectHandler.cs | 81 ++++++++++++++++--- .../Effects/ZoneEffects/ZoneEffectReader.cs | 24 +++--- .../ZoneEffects/ZoneEffectRepository.cs | 26 ------ src/Perpetuum/Zones/IZone.cs | 5 +- src/Perpetuum/Zones/Zone.cs | 8 +- src/Perpetuum/Zones/ZoneSession.cs | 25 +----- 26 files changed, 582 insertions(+), 119 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs create mode 100644 src/Perpetuum/Services/GameTime/GameTimeInfo.cs create mode 100644 src/Perpetuum/Services/GameTime/GameTimeObserver.cs create mode 100644 src/Perpetuum/Services/GameTime/GameTimeService.cs create mode 100644 src/Perpetuum/Services/GameTime/IGameTimeService.cs create mode 100644 src/Perpetuum/Services/Weather/IWeatherService.cs rename src/Perpetuum/{Zones => Services/Weather}/WeatherInfo.cs (55%) create mode 100644 src/Perpetuum/Services/Weather/WeatherObservers.cs rename src/Perpetuum/{Zones => Services/Weather}/WeatherService.cs (79%) delete mode 100644 src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 00cf9b3a6..ab7e80dae 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -79,6 +79,7 @@ using Perpetuum.Services; using Perpetuum.Services.Channels; using Perpetuum.Services.Channels.ChatCommands; +using Perpetuum.Services.Daytime; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Services.ExtensionService; @@ -112,6 +113,7 @@ using Perpetuum.Services.Steam; using Perpetuum.Services.TechTree; using Perpetuum.Services.Trading; +using Perpetuum.Services.Weather; using Perpetuum.Threading.Process; using Perpetuum.Units; using Perpetuum.Units.DockingBases; @@ -651,8 +653,13 @@ private void RegisterEffects() { _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType().As().SingleInstance(); + _builder.RegisterType().As(); + + _builder.Register>(x => + { + var ctx = x.Resolve(); + return zone => new ZoneEffectHandler(zone); + }); _builder.RegisterType().Keyed(EffectType.effect_invulnerable); _builder.RegisterType().Keyed(EffectType.effect_eccm); @@ -1704,6 +1711,14 @@ private void InitRelayManager() 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.Context.Resolve(); + }); + + _builder.RegisterType().As().SingleInstance().OnActivated(e => + { + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5))); + var obs = new GameTimeObserver(e.Context.Resolve()); + e.Instance.Subscribe(obs); }); // OPP: InterzoneNPCManager @@ -2477,6 +2492,17 @@ private void RegisterZones() e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5))); }).As(); + _builder.RegisterType(); + _builder.RegisterType(); + _builder.Register>(x => + { + var ctx = x.Resolve(); + return zone => + { + return new WeatherEventListener(ctx.Resolve(), zone); + }; + }); + _builder.RegisterType().AsSelf().As(); _builder.RegisterType().AsSelf().As(); @@ -2510,7 +2536,6 @@ void RegisterZone(ZoneType type) where T:Zone RegisterZone(ZoneType.Training); RegisterZone(ZoneType.Stronghold); - _builder.RegisterType(); _builder.RegisterType(); @@ -2522,6 +2547,7 @@ void RegisterZone(ZoneType type) where T:Zone var 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(); @@ -2546,6 +2572,11 @@ void RegisterZone(ZoneType type) where T:Zone zone.TerraformHandler = ctx.Resolve().Invoke(zone); } + ctx.Resolve().AttachListener(new WeatherWatcher(zone)); + ctx.Resolve().AttachListener(new GameTimeEventProcessor(zone)); + var listener = ctx.Resolve>().Invoke(zone); + listener.Subscribe(zone.Weather); + zone.LoadUnits(); return zone; }; diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index f08f6da8a..78304576d 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -52,6 +52,8 @@ public enum EffectCategory : long effcat_teleport_effects = 0x0000000008000000, effcat_terrain_object_effects = 0x0000080000000000, effcat_zero_core_drop = 0x0000000400000000, - effcat_zone_effects = 0x0000100000000000 // New Zone effects + effcat_zone_effects = 0x0000100000000000, // OPP: New Zone effects + effcat_weather_effects = 0x0000200000000000, // OPP: New Zone effects + effcat_daytime_effects = 0x0000400000000000 // OPP: New Zone effects } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 3b60a5665..0d8c643aa 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -106,6 +106,10 @@ public enum EffectType effect_beta_bonus = 103, effect_beta2_bonus = 104, effect_alpha_bonus = 105, - effect_alpha2_bonus = 106 + effect_alpha2_bonus = 106, + effect_weather_good = 107, + effect_weather_bad = 108, + effect_daytime_day = 109, + effect_daytime_night = 110 } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 79b54ad9f..8869a64d5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -289,17 +289,25 @@ + + + + + + + + @@ -548,6 +556,10 @@ + + + + @@ -616,10 +628,8 @@ - - @@ -684,7 +694,6 @@ - @@ -1156,8 +1165,6 @@ - - diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 3646f4a72..47730220f 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -136,7 +136,6 @@ public sealed class Player : Robot,IBlobableUnit,IBlobEmitter private readonly IBlobEmitter _blobEmitter; private readonly BlobHandler _blobHandler; private readonly PlayerMovement _movement; - private readonly IZoneEffectHandler _zoneEffectHandler; private CombatLogger _combatLogger; private PlayerMoveCheckQueue _check; @@ -145,8 +144,7 @@ public Player(IExtensionReader extensionReader, MissionHandler.Factory missionHandlerFactory, ITeleportStrategyFactories teleportStrategyFactories, DockingBaseHelper dockingBaseHelper, - CombatLogger.Factory combatLoggerFactory, - IZoneEffectHandler zoneEffectHandler + CombatLogger.Factory combatLoggerFactory ) { _extensionReader = extensionReader; @@ -155,7 +153,6 @@ IZoneEffectHandler zoneEffectHandler _teleportStrategyFactories = teleportStrategyFactories; _dockingBaseHelper = dockingBaseHelper; _combatLoggerFactory = combatLoggerFactory; - _zoneEffectHandler = zoneEffectHandler; Session = ZoneSession.None; _movement = new PlayerMovement(this); @@ -1059,7 +1056,6 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) player.AddToZone(zone,validPosition,zoneEnterType); player.ApplyInvulnerableEffect(); - player.ApplyZoneEffects(zone); }); scope.Complete(); @@ -1067,15 +1063,6 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) } } - /// - /// Apply static zone-level effects to player when they enter zone - /// - /// Izone - private void ApplyZoneEffects(IZone zone) - { - _zoneEffectHandler.ApplyZoneEffects(this, zone); - } - [CanBeNull] public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, bool applyInvulnerable) { diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index c7d6cee6d..508b6190f 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -2,6 +2,7 @@ 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; diff --git a/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs new file mode 100644 index 000000000..f9e3c4d20 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs @@ -0,0 +1,13 @@ +using Perpetuum.Services.Daytime; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class GameTimeMessage : EventMessage + { + public GameTimeInfo TimeInfo { get; private set; } + public GameTimeMessage(GameTimeInfo time) + { + TimeInfo = time; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs new file mode 100644 index 000000000..5dbdd851b --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs @@ -0,0 +1,15 @@ +using Perpetuum.Services.Weather; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class WeatherEventMessage : EventMessage + { + 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/GameTimeEventProcessor.cs b/src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs new file mode 100644 index 000000000..ff408295c --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs @@ -0,0 +1,60 @@ +using System; +using Perpetuum.ExportedTypes; +using Perpetuum.Services.Daytime; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.Effects.ZoneEffects; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + /// + /// GameTimeMessage processor to modify ZoneEffects on a zone + /// + public class GameTimeEventProcessor : EventProcessor + { + private readonly IZone _zone; + private readonly Lazy _dayEffect; + private readonly Lazy _nightEffect; + public GameTimeEventProcessor(IZone zone) + { + _zone = zone; + _dayEffect = new Lazy(CreateDayEffect); + _nightEffect = new Lazy(CreateNightEffect); + } + + private ZoneEffect CreateDayEffect() + { + return new ZoneEffect(_zone.Id, EffectType.effect_daytime_day, true); + } + + private ZoneEffect CreateNightEffect() + { + return new ZoneEffect(_zone.Id, EffectType.effect_daytime_night, true); + } + + public override void OnNext(EventMessage value) + { + if (value is GameTimeMessage msg) + { + if (msg.TimeInfo.IsNight) + { + _zone.ZoneEffectHandler.AddEffect(_nightEffect.Value); + } + else + { + _zone.ZoneEffectHandler.RemoveEffect(_nightEffect.Value); + } + + if (msg.TimeInfo.IsDay) + { + _zone.ZoneEffectHandler.AddEffect(_dayEffect.Value); + } + else + { + _zone.ZoneEffectHandler.RemoveEffect(_dayEffect.Value); + } + } + } + } +} + diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs b/src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs new file mode 100644 index 000000000..cba420cfd --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs @@ -0,0 +1,58 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Zones; +using Perpetuum.Zones.Effects.ZoneEffects; +using System; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + /// + /// WeatherEvent processor to modify ZoneEffects on a zone + /// + public class WeatherWatcher : EventProcessor + { + private readonly IZone _zone; + private readonly Lazy _goodWeather; + private readonly Lazy _badWeather; + public WeatherWatcher(IZone zone) + { + _zone = zone; + _goodWeather = new Lazy(CreateGoodWeather); + _badWeather = new Lazy(CreateBadWeather); + } + + private ZoneEffect CreateGoodWeather() + { + return new ZoneEffect(_zone.Id, EffectType.effect_weather_good, true); + } + + private ZoneEffect CreateBadWeather() + { + return new ZoneEffect(_zone.Id, EffectType.effect_weather_bad, true); + } + + public override void OnNext(EventMessage value) + { + if (value is WeatherEventMessage msg && msg.ZoneId == _zone.Id) + { + if (msg.Weather.IsBadWeather) + { + _zone.ZoneEffectHandler.AddEffect(_badWeather.Value); + } + else + { + _zone.ZoneEffectHandler.RemoveEffect(_badWeather.Value); + } + + if (msg.Weather.IsGoodWeather) + { + _zone.ZoneEffectHandler.AddEffect(_goodWeather.Value); + } + else + { + _zone.ZoneEffectHandler.RemoveEffect(_goodWeather.Value); + } + } + } + } +} diff --git a/src/Perpetuum/Services/GameTime/GameTimeInfo.cs b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs new file mode 100644 index 000000000..45d5f7300 --- /dev/null +++ b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs @@ -0,0 +1,52 @@ +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 bool IsDay + { + get + { + return GameTimeStamp > DAY_START && GameTimeStamp < DAY_END; + } + } + + public bool IsNight + { + get + { + return GameTimeStamp > NIGHT_START || GameTimeStamp < NIGHT_END; + } + } + } +} 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/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/Zones/WeatherInfo.cs b/src/Perpetuum/Services/Weather/WeatherInfo.cs similarity index 55% rename from src/Perpetuum/Zones/WeatherInfo.cs rename to src/Perpetuum/Services/Weather/WeatherInfo.cs index ff1c75873..40b336e20 100644 --- a/src/Perpetuum/Zones/WeatherInfo.cs +++ b/src/Perpetuum/Services/Weather/WeatherInfo.cs @@ -1,19 +1,23 @@ using System; using System.Text; using Perpetuum.Timers; +using Perpetuum.Zones; -namespace Perpetuum.Zones +namespace Perpetuum.Services.Weather { public class WeatherInfo { - private readonly int _current; - public int Next { get; private set; } + private const int BAD_WEATHER = 200; + private const int GOOD_WEATHER = 100; + + public readonly int Current; + public readonly int Next; private readonly TimeTracker _timer; - public WeatherInfo(int current, int next,TimeSpan duration) + public WeatherInfo(int current, int next, TimeSpan duration) { - _current = current; + Current = current; Next = next; _timer = new TimeTracker(duration); } @@ -28,7 +32,7 @@ public Packet CreateUpdatePacket() { var packet = new Packet(ZoneCommand.Weather); packet.AppendByte(0); // ez lesz majd a - packet.AppendByte((byte)_current); + packet.AppendByte((byte)Current); packet.AppendByte((byte)Next); packet.AppendLong((long)_timer.Elapsed.TotalMilliseconds); packet.AppendLong((long)_timer.Duration.TotalMilliseconds); @@ -39,8 +43,24 @@ 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); + ZoneCommand.Weather, Current, Next, _timer.Elapsed.TotalSeconds, _timer.Duration.TotalSeconds); return sb.ToString(); } + + public bool IsBadWeather + { + get + { + return Current > BAD_WEATHER && Next > BAD_WEATHER; + } + } + + public bool IsGoodWeather + { + get + { + return Current < GOOD_WEATHER && Next < GOOD_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 79% rename from src/Perpetuum/Zones/WeatherService.cs rename to src/Perpetuum/Services/Weather/WeatherService.cs index 3854d3638..110d605d4 100644 --- a/src/Perpetuum/Zones/WeatherService.cs +++ b/src/Perpetuum/Services/Weather/WeatherService.cs @@ -2,14 +2,14 @@ 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 readonly TimeRange _updateInterval; - private readonly Observable _observable; + private readonly Observable _observable; private WeatherInfo _current; @@ -26,19 +26,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); } @@ -56,7 +56,7 @@ public override void Update(TimeSpan time) SendWeatherUpdate(_current); } - public IDisposable Subscribe(IObserver observer) + public IDisposable Subscribe(IObserver observer) { return _observable.Subscribe(observer); } @@ -68,7 +68,7 @@ public WeatherInfo GetCurrentWeather() private void SendWeatherUpdate(WeatherInfo info) { - _observable.OnNext(info.CreateUpdatePacket()); + _observable.OnNext(info); } public void SetCurrentWeather(WeatherInfo weather) diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs index 6161c5a5c..45ba99b84 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/IZoneEffectHandler.cs @@ -1,4 +1,4 @@ -using Perpetuum.Players; +using Perpetuum.Units; namespace Perpetuum.Zones.Effects.ZoneEffects { @@ -7,6 +7,22 @@ namespace Perpetuum.Zones.Effects.ZoneEffects /// public interface IZoneEffectHandler { - void ApplyZoneEffects(Player player, IZone zone); + /// + /// 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/ZoneEffect.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs index 33048a2f8..8dd03b439 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffect.cs @@ -7,13 +7,43 @@ namespace Perpetuum.Zones.Effects.ZoneEffects /// public class ZoneEffect { - public IZone Zone { get; private set; } + public int ZoneId { get; private set; } public EffectType Effect { get; private set; } + public bool PlayerOnly { get; private set; } - public ZoneEffect(IZone zone, EffectType effectType) + public ZoneEffect(int zoneId, EffectType effectType, bool forPlayersOnly) { - Zone = zone; + 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 index 1ba9fdbb6..29fff6619 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs @@ -1,22 +1,85 @@ -using Perpetuum.Players; +using Perpetuum.Log; +using Perpetuum.Players; +using Perpetuum.Units; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Zones.Effects.ZoneEffects { public class ZoneEffectHandler : IZoneEffectHandler { - private readonly IZoneEffectRepository _zoneEffectRepository; - public ZoneEffectHandler(IZoneEffectRepository zoneEffectRepository) + 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) + { + _effects.TryAdd(zoneEffect, byte.MinValue); + OnZoneEffectAdded(zoneEffect); + } + + public void RemoveEffect(ZoneEffect zoneEffect) + { + _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) { - _zoneEffectRepository = zoneEffectRepository; + return u => !u.EffectHandler.ContainsEffect(effect.Effect) && (!effect.PlayerOnly || u is Player); } - public void ApplyZoneEffects(Player player, IZone zone) + public void OnEnterZone(Unit unit) { - var zoneEffects = _zoneEffectRepository.GetZoneEffects(zone); - foreach (var zoneEffect in zoneEffects) + foreach (var zoneEffect in GetEffects()) { - var builder = player.NewEffectBuilder().SetType(zoneEffect.Effect).SetOwnerToSource(); - player.ApplyEffect(builder); + 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 index c5be8f335..46b9b4473 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectReader.cs @@ -11,22 +11,16 @@ namespace Perpetuum.Zones.Effects.ZoneEffects /// /// DB querying object for ZoneEffects /// - public class ZoneEffectReader + public static class ZoneEffectReader { - private readonly IZone _zone; - - public ZoneEffectReader(IZone zone) - { - _zone = zone; - } - - private ZoneEffect CreateZoneEffectFromRecord(IDataRecord record) + private static ZoneEffect CreateZoneEffectFromRecord(IDataRecord record) { try { - var value = record.GetValue("effectid"); - var effectType = EnumHelper.GetEnum(value); - var config = new ZoneEffect(_zone, effectType); + 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) @@ -36,10 +30,10 @@ private ZoneEffect CreateZoneEffectFromRecord(IDataRecord record) return null; } - public IEnumerable GetZoneEffects() + public static IEnumerable GetStaticZoneEffects(IZone zone) { - var zoneEffects = Db.Query().CommandText("SELECT zoneid, effectid FROM zoneeffects WHERE zoneid = @zoneId") - .SetParameter("@zoneId", _zone.Id) + var zoneEffects = Db.Query().CommandText("SELECT zoneid, effectid FROM zoneeffects WHERE zoneid=@zoneId") + .SetParameter("@zoneId", zone.Id) .Execute() .Select(CreateZoneEffectFromRecord); diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs deleted file mode 100644 index 935e83972..000000000 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectRepository.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace Perpetuum.Zones.Effects.ZoneEffects -{ - public class ZoneEffectRepository : IZoneEffectRepository - { - private readonly ConcurrentDictionary> cache; - public ZoneEffectRepository() - { - cache = new ConcurrentDictionary>(); - } - - private List GetZoneEffectIfNotCached(IZone zone) - { - var zoneEffectReader = new ZoneEffectReader(zone); - return zoneEffectReader.GetZoneEffects().ToList(); - } - - public List GetZoneEffects(IZone zone) - { - return cache.GetOrAdd(zone.Id, () => GetZoneEffectIfNotCached(zone)); - } - } -} diff --git a/src/Perpetuum/Zones/IZone.cs b/src/Perpetuum/Zones/IZone.cs index 91994ed91..8c9cf76e2 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -6,9 +6,11 @@ using Perpetuum.Log; using Perpetuum.Players; using Perpetuum.Services.Relics; +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; @@ -49,6 +51,7 @@ public interface IZone TerraformHandler TerraformHandler { get; } MiningLogHandler MiningLogHandler { get; } IRelicManager RelicManager { get; } + IZoneEffectHandler ZoneEffectHandler { get; } IZoneUnitService UnitService { get; } IZoneEnterQueueService EnterQueueService { get; } @@ -59,6 +62,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/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 1901d7f6b..f7b06f3d9 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -18,12 +18,13 @@ using Perpetuum.Services.Relics; using Perpetuum.Services.RiftSystem; using Perpetuum.Services.Sessions; +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; +using Perpetuum.Zones.Effects.ZoneEffects; using Perpetuum.Zones.Environments; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.SafeSpawnPoints; @@ -38,7 +39,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; @@ -64,6 +65,7 @@ public abstract class Zone : Threading.Process.Process,IZone public IZoneUnitService UnitService { get; set; } public MiningLogHandler MiningLogHandler { get; set; } public ZoneSession.Factory ZoneSessionFactory { get; set; } + public IZoneEffectHandler ZoneEffectHandler { get; set; } [CanBeNull] public IRiftManager RiftManager { private get; set; } @@ -201,6 +203,8 @@ public void AddUnit(Unit unit) unit.Updated += OnUnitUpdated; unit.Dead += OnUnitDead; + + ZoneEffectHandler.OnEnterZone(unit); Logger.Info($"Unit entered to zone. zone:{Id} eid = {unit.InfoString} ({unit.CurrentPosition})"); } diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index 6b66a98fc..4c5788ff6 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; @@ -284,9 +285,9 @@ private void HandleAuth(Packet packet) _player = player; } - private void OnWeatherUpdated(Packet weatherUpdatePacket) + private void OnWeatherUpdated(WeatherInfo weather) { - SendPacket(weatherUpdatePacket); + SendPacket(weather.CreateUpdatePacket()); } private TerrainUpdateNotifier CreateTerrainNotifier(Player player) @@ -928,26 +929,6 @@ public void Update(TimeSpan time) UpdateLogout(time); } - private class WeatherMonitor: Observer - { - private Action _onNext; - public WeatherMonitor(Action onNext) - { - _onNext = onNext; - } - - public override void OnNext(Packet packet) - { - _onNext(packet); - } - - protected override void OnDispose() - { - _onNext = null; - base.OnDispose(); - } - } - private class BeamsMonitor : Observer { private readonly ZoneSession _session; From bbfcdb2cdce74d703f7fc36c0bfa3cf6ec365c3c Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 31 Oct 2020 22:34:11 -0400 Subject: [PATCH 223/457] wrong subscribe method, resolving things, IoC hell (#234) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index ab7e80dae..c42a11fbc 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1711,14 +1711,13 @@ private void InitRelayManager() 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.Context.Resolve(); + var obs = new GameTimeObserver(e.Instance); + obs.Subscribe(e.Context.Resolve()); }); _builder.RegisterType().As().SingleInstance().OnActivated(e => { - e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5))); - var obs = new GameTimeObserver(e.Context.Resolve()); - e.Instance.Subscribe(obs); + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(15))); }); // OPP: InterzoneNPCManager From 2d35ea341aa38f07be66167096e9ffadf41ab102 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 31 Oct 2020 22:35:36 -0400 Subject: [PATCH 224/457] Fixes on terrain update (#235) --- src/Perpetuum/Zones/Terrains/Layer.cs | 7 +++++++ src/Perpetuum/Zones/Terrains/TerrainUpdateNotifier.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) 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/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 From 483e7133919eeefff962e45160a99d3fe3b5b845 Mon Sep 17 00:00:00 2001 From: DerekJarvis <6828105+DerekJarvis@users.noreply.github.com> Date: Thu, 19 Nov 2020 01:04:39 +0000 Subject: [PATCH 225/457] What's on second. (#237) --- src/Perpetuum/Zones/ZoneSession.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index 4c5788ff6..c2f6ad90b 100644 --- a/src/Perpetuum/Zones/ZoneSession.cs +++ b/src/Perpetuum/Zones/ZoneSession.cs @@ -235,11 +235,12 @@ 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); @@ -256,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; From d5dce0cbfa69f04d2552a9c6edda61badd1473f0 Mon Sep 17 00:00:00 2001 From: DerekJarvis <6828105+DerekJarvis@users.noreply.github.com> Date: Thu, 19 Nov 2020 03:01:54 +0000 Subject: [PATCH 226/457] Rally terminals (field terminals with no assignments) (#236) --- src/Perpetuum.ExportedTypes/DefinitionNames.cs | 1 + .../Missions/MissionGetOptions.cs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 0bd941f45..6d9656651 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -970,6 +970,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"; 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); } From a1cb3565fcff89b42ef5388712cc3abcabe7e4db Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 29 Nov 2020 15:45:40 -0500 Subject: [PATCH 227/457] Gate PVP flag (#243) --- src/Perpetuum/Zones/Gates/Gate.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Gates/Gate.cs b/src/Perpetuum/Zones/Gates/Gate.cs index 83f9c90cf..c341d982f 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,14 @@ 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(); + } + } } } \ No newline at end of file From 8fc80c887e7bfa28ff2542d048d0daf009f4ae04 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 2 Dec 2020 20:28:58 -0500 Subject: [PATCH 228/457] Combine Weather/Gametime effects (#244) --- .../PerpetuumBootstrapper.cs | 15 +- src/Perpetuum.ExportedTypes/EffectCategory.cs | 3 +- src/Perpetuum.ExportedTypes/EffectType.cs | 12 +- src/Perpetuum/Perpetuum.csproj | 3 +- .../EnvironmentalEffectHandler.cs | 150 ++++++++++++++++++ .../EventProcessors/GameTimeEventProcessor.cs | 60 ------- .../EventProcessors/WeatherWatcher.cs | 58 ------- 7 files changed, 173 insertions(+), 128 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs delete mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs delete mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index c42a11fbc..93e38b130 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2502,6 +2502,17 @@ private void RegisterZones() }; }); + _builder.Register>(x => + { + var ctx = x.Resolve(); + return zone => + { + var listener = new EnvironmentalEffectHandler(zone); + ctx.Resolve().AttachListener(listener); + return listener; + }; + }); + _builder.RegisterType().AsSelf().As(); _builder.RegisterType().AsSelf().As(); @@ -2571,8 +2582,6 @@ void RegisterZone(ZoneType type) where T:Zone zone.TerraformHandler = ctx.Resolve().Invoke(zone); } - ctx.Resolve().AttachListener(new WeatherWatcher(zone)); - ctx.Resolve().AttachListener(new GameTimeEventProcessor(zone)); var listener = ctx.Resolve>().Invoke(zone); listener.Subscribe(zone.Weather); @@ -2589,6 +2598,8 @@ void RegisterZone(ZoneType type) where T:Zone var zoneFactory = e.Context.Resolve>(); var zone = zoneFactory(c); + e.Context.Resolve>().Invoke(zone); + Logger.Info("------------------"); Logger.Info("--"); Logger.Info("-- zone " + zone.Configuration.Id + " loaded."); diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 78304576d..8b831bf54 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -53,7 +53,6 @@ public enum EffectCategory : long effcat_terrain_object_effects = 0x0000080000000000, effcat_zero_core_drop = 0x0000000400000000, effcat_zone_effects = 0x0000100000000000, // OPP: New Zone effects - effcat_weather_effects = 0x0000200000000000, // OPP: New Zone effects - effcat_daytime_effects = 0x0000400000000000 // OPP: New Zone effects + effcat_environmental_effects = 0x0000200000000000 // OPP: New Zone effects } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 0d8c643aa..a1559eb69 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -107,9 +107,13 @@ public enum EffectType effect_beta2_bonus = 104, effect_alpha_bonus = 105, effect_alpha2_bonus = 106, - effect_weather_good = 107, - effect_weather_bad = 108, - effect_daytime_day = 109, - effect_daytime_night = 110 + 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 } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 8869a64d5..5d6492be5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -289,6 +289,7 @@ + @@ -304,10 +305,8 @@ - - diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs new file mode 100644 index 000000000..26452030a --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -0,0 +1,150 @@ +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 EffectType[] _effectTypes = new EffectType[] { + EffectType.effect_day, + EffectType.effect_day_clear, + EffectType.effect_day_overcast, + EffectType.effect_night, + EffectType.effect_night_clear, + EffectType.effect_night_overcast, + EffectType.effect_weather_good, + EffectType.effect_weather_bad + }; + private readonly IDictionary _effects = new Dictionary(); + public EnvironmentalEffectHandler(IZone zone) + { + _zone = zone; + _effects = InitEffectCollection(_effectTypes); + } + + private IDictionary InitEffectCollection(EffectType[] effectTypes) + { + var dict = new Dictionary(); + foreach (var effType in effectTypes) + { + dict.Add(effType, new ZoneEffect(_zone.Id, effType, true)); + } + return dict; + } + + private ZoneEffect GetEffect(EffectType type) + { + if (_effects.TryGetValue(type, out ZoneEffect effect)) + { + return effect; + } + return null; + } + + private void OnStateChange() + { + if (_gameTime == null || _weatherState == null) + return; + + ZoneEffect nextEffect = null; + if (_gameTime.IsDay) + { + if (_weatherState.IsGoodWeather) + { + nextEffect = GetEffect(EffectType.effect_day_clear); + } + else if (_weatherState.IsBadWeather) + { + nextEffect = GetEffect(EffectType.effect_day_overcast); + } + else + { + nextEffect = GetEffect(EffectType.effect_day); + } + } + else if (_gameTime.IsNight) + { + if (_weatherState.IsGoodWeather) + { + nextEffect = GetEffect(EffectType.effect_night_clear); + } + else if (_weatherState.IsBadWeather) + { + nextEffect = GetEffect(EffectType.effect_night_overcast); + } + else + { + nextEffect = GetEffect(EffectType.effect_night); + } + } + else + { + if (_weatherState.IsGoodWeather) + { + nextEffect = GetEffect(EffectType.effect_weather_good); + } + else if (_weatherState.IsBadWeather) + { + nextEffect = GetEffect(EffectType.effect_weather_bad); + } + } + + var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || + (_currentEffect != null && _currentEffect.Equals(nextEffect)); + + if (!isSameEffect) + { + if (_currentEffect != null) + { + _zone.ZoneEffectHandler.RemoveEffect(_currentEffect); + } + _zone.ZoneEffectHandler.AddEffect(nextEffect); + _currentEffect = nextEffect; + } + } + + private bool TryGetWeatherMessage(EventMessage value) + { + var msg = value as WeatherEventMessage; + var isValidMsg = msg != null && msg.ZoneId == _zone.Id; + if (isValidMsg) + { + _weatherState = msg.Weather; + } + return isValidMsg; + } + + private bool TryGetGameTimeMessage(EventMessage value) + { + var msg = value as GameTimeMessage; + var isValidMsg = msg != null; + if (isValidMsg) + { + _gameTime = msg.TimeInfo; + } + return isValidMsg; + } + + public override void OnNext(EventMessage value) + { + var stateChange = TryGetWeatherMessage(value) || TryGetGameTimeMessage(value); + if (stateChange) + { + OnStateChange(); + } + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs b/src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs deleted file mode 100644 index ff408295c..000000000 --- a/src/Perpetuum/Services/EventServices/EventProcessors/GameTimeEventProcessor.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Perpetuum.ExportedTypes; -using Perpetuum.Services.Daytime; -using Perpetuum.Services.EventServices.EventMessages; -using Perpetuum.Zones; -using Perpetuum.Zones.Effects.ZoneEffects; - -namespace Perpetuum.Services.EventServices.EventProcessors -{ - /// - /// GameTimeMessage processor to modify ZoneEffects on a zone - /// - public class GameTimeEventProcessor : EventProcessor - { - private readonly IZone _zone; - private readonly Lazy _dayEffect; - private readonly Lazy _nightEffect; - public GameTimeEventProcessor(IZone zone) - { - _zone = zone; - _dayEffect = new Lazy(CreateDayEffect); - _nightEffect = new Lazy(CreateNightEffect); - } - - private ZoneEffect CreateDayEffect() - { - return new ZoneEffect(_zone.Id, EffectType.effect_daytime_day, true); - } - - private ZoneEffect CreateNightEffect() - { - return new ZoneEffect(_zone.Id, EffectType.effect_daytime_night, true); - } - - public override void OnNext(EventMessage value) - { - if (value is GameTimeMessage msg) - { - if (msg.TimeInfo.IsNight) - { - _zone.ZoneEffectHandler.AddEffect(_nightEffect.Value); - } - else - { - _zone.ZoneEffectHandler.RemoveEffect(_nightEffect.Value); - } - - if (msg.TimeInfo.IsDay) - { - _zone.ZoneEffectHandler.AddEffect(_dayEffect.Value); - } - else - { - _zone.ZoneEffectHandler.RemoveEffect(_dayEffect.Value); - } - } - } - } -} - diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs b/src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs deleted file mode 100644 index cba420cfd..000000000 --- a/src/Perpetuum/Services/EventServices/EventProcessors/WeatherWatcher.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Perpetuum.ExportedTypes; -using Perpetuum.Services.EventServices.EventMessages; -using Perpetuum.Zones; -using Perpetuum.Zones.Effects.ZoneEffects; -using System; - -namespace Perpetuum.Services.EventServices.EventProcessors -{ - /// - /// WeatherEvent processor to modify ZoneEffects on a zone - /// - public class WeatherWatcher : EventProcessor - { - private readonly IZone _zone; - private readonly Lazy _goodWeather; - private readonly Lazy _badWeather; - public WeatherWatcher(IZone zone) - { - _zone = zone; - _goodWeather = new Lazy(CreateGoodWeather); - _badWeather = new Lazy(CreateBadWeather); - } - - private ZoneEffect CreateGoodWeather() - { - return new ZoneEffect(_zone.Id, EffectType.effect_weather_good, true); - } - - private ZoneEffect CreateBadWeather() - { - return new ZoneEffect(_zone.Id, EffectType.effect_weather_bad, true); - } - - public override void OnNext(EventMessage value) - { - if (value is WeatherEventMessage msg && msg.ZoneId == _zone.Id) - { - if (msg.Weather.IsBadWeather) - { - _zone.ZoneEffectHandler.AddEffect(_badWeather.Value); - } - else - { - _zone.ZoneEffectHandler.RemoveEffect(_badWeather.Value); - } - - if (msg.Weather.IsGoodWeather) - { - _zone.ZoneEffectHandler.AddEffect(_goodWeather.Value); - } - else - { - _zone.ZoneEffectHandler.RemoveEffect(_goodWeather.Value); - } - } - } - } -} From 19272c5c64b17eae82b302e734aa172d9efc6607 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 7 Dec 2020 22:12:21 -0500 Subject: [PATCH 229/457] Npc Reinforcements: Impl boss reinforcements (#240) --- .../PerpetuumBootstrapper.cs | 6 +- src/Perpetuum/Perpetuum.csproj | 7 +- .../EventServices/EventListenerService.cs | 17 +- .../EventMessages/NpcReinforcementsMessage.cs | 16 ++ .../EventProcessors/AffectOutpostStability.cs | 4 +- .../EventServices/EventProcessors/ChatEcho.cs | 8 +- .../EnvironmentalEffectHandler.cs | 5 +- .../EventProcessors/EventProcessor.cs | 25 ++- .../NpcReinforcementSpawner.cs | 124 ++++++++++++ .../NpcSpawnEventHandler.cs | 127 ++++++++++++ .../NpcSpawnEventHandlers/OreNPCSpawner.cs | 116 +++++++++++ .../EventProcessors/OreNPCSpawner.cs | 190 ------------------ .../RandomWalkableAroundPositionFinder.cs | 63 ++++++ .../PositionFinders/RandomWalkableOnCircle.cs | 29 +++ .../Zones/NpcSystem/Flocks/NormalFlock.cs | 6 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 62 +++++- src/Perpetuum/Zones/NpcSystem/Npc.cs | 9 +- .../Reinforcements/INpcReinforcements.cs | 6 +- .../Reinforcements/NpcReinforcements.cs | 10 +- .../NpcReinforcementsRepository.cs | 8 +- .../Materials/Minerals/MineralLayer.cs | 2 +- .../Materials/Minerals/MineralNode.cs | 2 +- src/Perpetuum/Zones/ZoneExtensions.Npc.cs | 4 +- 23 files changed, 609 insertions(+), 237 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs delete mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs create mode 100644 src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableAroundPositionFinder.cs create mode 100644 src/Perpetuum/Zones/Finders/PositionFinders/RandomWalkableOnCircle.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 93e38b130..95ee59d42 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -81,7 +81,9 @@ 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; @@ -1705,7 +1707,8 @@ private void InitRelayManager() _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))); @@ -2582,6 +2585,7 @@ void RegisterZone(ZoneType type) where T:Zone zone.TerraformHandler = ctx.Resolve().Invoke(zone); } + ctx.Resolve().AttachListener(new NpcReinforcementSpawner(zone, ctx.Resolve())); var listener = ctx.Resolve>().Invoke(zone); listener.Subscribe(zone.Weather); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 5d6492be5..9b8904032 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -289,6 +289,9 @@ + + + @@ -306,7 +309,7 @@ - + @@ -630,6 +633,8 @@ + + diff --git a/src/Perpetuum/Services/EventServices/EventListenerService.cs b/src/Perpetuum/Services/EventServices/EventListenerService.cs index f894af5bd..7959688a3 100644 --- a/src/Perpetuum/Services/EventServices/EventListenerService.cs +++ b/src/Perpetuum/Services/EventServices/EventListenerService.cs @@ -13,12 +13,13 @@ namespace Perpetuum.Services.EventServices /// public class EventListenerService : Process { - private readonly IList> _observers; + private readonly object _lock = new object(); + private readonly IList _observers; private readonly ConcurrentQueue _queue; public EventListenerService() { - _observers = new List>(); + _observers = new List(); _queue = new ConcurrentQueue(); } @@ -33,9 +34,12 @@ public void PublishMessage(EventMessage message) public void NotifyListeners(EventMessage message) { - foreach (var obs in _observers) + lock (_lock) { - obs.OnNext(message); + foreach (var obs in _observers) + { + obs.OnNext(message); + } } } @@ -43,9 +47,10 @@ public void NotifyListeners(EventMessage message) /// Listeners are subscribed and instantiated in the Bootstrapper /// /// Listener - public void AttachListener(EventProcessor observer) + public void AttachListener(IEventProcessor observer) { - _observers.Add(observer); + lock (_lock) + _observers.Add(observer); } public override void Update(TimeSpan time) diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs new file mode 100644 index 000000000..29c52472d --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs @@ -0,0 +1,16 @@ +using Perpetuum.Zones.NpcSystem; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class NpcReinforcementsMessage : EventMessage + { + public Npc Npc { get; } + public int ZoneId { get; } + + public NpcReinforcementsMessage(Npc npc, int zoneID) + { + ZoneId = zoneID; + Npc = npc; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs index abd68c8ab..45379fb56 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs @@ -6,7 +6,7 @@ namespace Perpetuum.Services.EventServices.EventProcessors /// /// Processes StabilityAffectingEvent messages for the provided Outpost /// - public class AffectOutpostStability : EventProcessor + public class AffectOutpostStability : EventProcessor { private Outpost _outpost; public AffectOutpostStability(Outpost outpost) @@ -14,7 +14,7 @@ public AffectOutpostStability(Outpost outpost) _outpost = outpost; } - public override void OnNext(EventMessage value) + public override void HandleMessage(EventMessage value) { if (value is StabilityAffectingEvent msg) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs index c3c27c1a5..809e056b7 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs @@ -10,7 +10,7 @@ namespace Perpetuum.Services.EventServices /// /// Simple EventProcessor example for demonstrating the EventListener system /// - public class ChatEcho : EventProcessor + public class ChatEcho : EventProcessor { private readonly IChannelManager _channelManager; private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; @@ -22,7 +22,7 @@ public ChatEcho(IChannelManager channelManager) _channelManager = channelManager; } - public override void OnNext(EventMessage value) + public override void HandleMessage(EventMessage value) { if (value is EventMessageSimple msg) { @@ -37,7 +37,7 @@ public override void OnNext(EventMessage value) /// NPC vicinity Chat Echo event handler /// Emits a message from an NPC on the Vicinity chat channel /// - public class NpcChatEcho : EventProcessor + public class NpcChatEcho : EventProcessor { private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; //TODO "Nian" character private Character _announcer; @@ -47,7 +47,7 @@ public NpcChatEcho() _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); } - public override void OnNext(EventMessage value) + public override void HandleMessage(EventMessage value) { if (value is NpcMessage msg) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 26452030a..ab135d4d2 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -4,7 +4,6 @@ using Perpetuum.Services.Weather; using Perpetuum.Zones; using Perpetuum.Zones.Effects.ZoneEffects; -using System; using System.Collections.Generic; namespace Perpetuum.Services.EventServices.EventProcessors @@ -12,7 +11,7 @@ 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 + public class EnvironmentalEffectHandler : EventProcessor { private readonly IZone _zone; private WeatherInfo _weatherState; @@ -138,7 +137,7 @@ private bool TryGetGameTimeMessage(EventMessage value) return isValidMsg; } - public override void OnNext(EventMessage value) + public override void HandleMessage(EventMessage value) { var stateChange = TryGetWeatherMessage(value) || TryGetGameTimeMessage(value); if (stateChange) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs index f459ef1ed..464dc98a7 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs @@ -1,21 +1,28 @@ -using System; +using Perpetuum.Services.EventServices.EventMessages; +using System; namespace Perpetuum.Services.EventServices.EventProcessors { - /// - /// Base class for all EventProcessors - /// - /// - public abstract class EventProcessor : IObserver + public interface IEventProcessor : IObserver { - public abstract void OnNext(EventMessage value); + void HandleMessage(EventMessage value); + } + + public abstract class EventProcessor : IEventProcessor + { + public abstract void HandleMessage(EventMessage value); + + public void OnNext(EventMessage value) + { + HandleMessage(value); + } - void IObserver.OnCompleted() + public void OnCompleted() { throw new NotImplementedException(); } - void IObserver.OnError(Exception error) + 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..f003b0e92 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -0,0 +1,124 @@ +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(10); } } + protected override TimeSpan SPAWN_LIFETIME { get { return TimeSpan.FromMinutes(15); } } + protected override int MAX_SPAWN_DIST { get { return 10; } } + + 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(EventMessage 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.Npc.BossInfo; + if (!_reinforcementsByNpc.ContainsKey(info)) + { + var reinforcements = _npcReinforcementsRepo.CreateNpcBossAddSpawn(info, msg.ZoneId); + _reinforcementsByNpc.Add(info, reinforcements); + } + } + + protected override bool CheckState(NpcReinforcementsMessage msg) + { + if (msg.Npc.BossInfo.IsDead) + { + CleanupAllReinforcements(msg); + return true; + } + UpdateAggro(msg); + return false; + } + + private void UpdateAggro(NpcReinforcementsMessage msg) + { + var info = msg.Npc.BossInfo; + if (_reinforcementsByNpc.ContainsKey(info)) + { + var activeWaves = _reinforcementsByNpc[info].GetAllActiveWaves().Where(w => w.ActivePresence != null); + foreach (var wave in activeWaves) + { + SpreadAggro(wave.ActivePresence, msg.Npc); + } + } + } + + protected override void CleanupAllReinforcements(NpcReinforcementsMessage msg) + { + var info = msg.Npc.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.Npc.CurrentPosition, maxRange); + if (finder.Find(out Position result)) + { + return result; + } + return msg.Npc.CurrentPosition; + } + + protected override INpcReinforcementWave GetNextWave(NpcReinforcementsMessage msg) + { + var npc = msg.Npc; + var percent = 1.0 - npc.ArmorPercentage; + return _reinforcementsByNpc[npc.BossInfo].GetNextPresence(percent); + } + + protected override void OnSpawning(Presence pres, NpcReinforcementsMessage msg) + { + SpreadAggro(pres, msg.Npc); + } + + private void SpreadAggro(Presence presenceToAggro, Npc npcWithAggro) + { + foreach (var npc in presenceToAggro.Flocks.GetMembers()) + { + foreach (var threat in npcWithAggro.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..10d394b47 --- /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 : EventMessage + { + 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(EventMessage 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(EventMessage 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..87fe22868 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs @@ -0,0 +1,116 @@ +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; + + 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(EventMessage 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/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs deleted file mode 100644 index 13e14130a..000000000 --- a/src/Perpetuum/Services/EventServices/EventProcessors/OreNPCSpawner.cs +++ /dev/null @@ -1,190 +0,0 @@ -using Perpetuum.ExportedTypes; -using Perpetuum.Log; -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; -using System.Threading.Tasks; - -namespace Perpetuum.Services.EventServices.EventProcessors -{ - /// - /// EventListener for each zone, receives messages for mineralnode mined and spawns npc presence based on the INpcReinforcementsRepository configurations - /// - public class OreNpcSpawner : EventProcessor - { - private const int SPAWN_DIST_FROM_FIELD = 100; - private const int MIN_SPAWN_DIST_TOLERANCE = 30; - private const int SPAWN_AREA_REQUIRED_SIZE = 3000; - private const int POSITION_FIND_ATTEMPTS = 10; - private readonly TimeSpan ORE_SPAWN_LIFETIME = TimeSpan.FromHours(3); - private readonly TimeSpan SPAWN_DELAY = TimeSpan.FromSeconds(10); - - private readonly IZone _zone; - private readonly IDictionary reinforcementsByMineralNode = new Dictionary(); - private readonly INpcReinforcementsRepository _npcReinforcementsRepo; - private readonly IEnumerable _mineralConfigs; - - public OreNpcSpawner(IZone zone, INpcReinforcementsRepository reinforcementsRepo, IMineralConfigurationReader mineralConfigurationReader) - { - _zone = zone; - _npcReinforcementsRepo = reinforcementsRepo; - _mineralConfigs = mineralConfigurationReader.ReadAll().Where(c => c.ZoneId == zone.Id); - } - - private void OnPresenceExpired(Presence presence) - { - var matchedEntries = reinforcementsByMineralNode.Where(p => p.Value.HasActivePresence(presence)).ToList(); - foreach (var pair in matchedEntries) - { - var wave = pair.Value.GetActiveWaveOfPresence(presence); - ExpireWave(wave); - } - } - - private void CleanupMineralNodeReinforcements(MineralNode node) - { - if (reinforcementsByMineralNode.ContainsKey(node)) - { - var activeWaves = reinforcementsByMineralNode[node].GetAllActiveWaves(); - foreach (var wave in activeWaves) - { - ExpireWave(wave); - } - reinforcementsByMineralNode.Remove(node); - } - } - - private void ExpireWave(INpcReinforcementWave wave) - { - wave.ActivePresence.PresenceExpired -= OnPresenceExpired; - wave.DeactivatePresence(); - } - - private Position TryFindSpawnLocation(Position start, double range) - { - for (int i = 0; i < POSITION_FIND_ATTEMPTS; i++) - { - var random = FastRandom.NextDouble(0.0, 1.0); - var pos = start.OffsetInDirection(random, range); - var posFinder = new ClosestWalkablePositionFinder(_zone, pos); - posFinder.Find(out Position p); - var result = _zone.FindWalkableArea(p, _zone.Size.ToArea(), SPAWN_AREA_REQUIRED_SIZE); - if (result != null && p.TotalDistance2D(start) > range - MIN_SPAWN_DIST_TOLERANCE) - { - return p; - } - } - 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; - } - - private void CheckMineralNodeReinforcements(OreNpcSpawnMessage msg) - { - var node = msg.Node; - if (!reinforcementsByMineralNode.ContainsKey(node)) - { - var oreSpawn = _npcReinforcementsRepo.CreateOreNPCSpawn(node.Type, msg.ZoneId); - reinforcementsByMineralNode.Add(node, oreSpawn); - } - } - - private bool IsNodeDead(OreNpcSpawnMessage msg) - { - if (msg.NodeState == OreNodeState.Removed) - { - CleanupMineralNodeReinforcements(msg.Node); - return true; - } - return false; - } - - private Position FindSpawnPosition(OreNpcSpawnMessage msg) - { - var fieldCenter = msg.Node.Area.Center.ToPosition(); - return TryFindSpawnLocation(fieldCenter, SPAWN_DIST_FROM_FIELD); - } - - private INpcReinforcementWave GetNextWave(OreNpcSpawnMessage msg) - { - var node = msg.Node; - var percent = ComputeFieldPercentConsumed(node); - return reinforcementsByMineralNode[node].GetNextPresence(percent); - } - - private Position GetOreFieldCenter(OreNpcSpawnMessage msg) - { - return msg.Node.Area.Center.ToPosition(); - } - - private 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)); - } - - private void DoSpawning(INpcReinforcementWave wave, Position homePosition, Position spawnPosition, MineralNode node) - { - var pres = _zone.AddDynamicPresenceToPosition(wave.PresenceId, homePosition, spawnPosition, ORE_SPAWN_LIFETIME); - pres.PresenceExpired += OnPresenceExpired; - wave.SetActivePresence(pres); - } - - private bool _spawning = false; - - public override void OnNext(EventMessage value) - { - if (value is OreNpcSpawnMessage msg && _zone.Id == msg.ZoneId) - { - if (_spawning) - return; - - CheckMineralNodeReinforcements(msg); - - if (IsNodeDead(msg)) - return; - - var spawnPos = FindSpawnPosition(msg); - if (spawnPos == Position.Empty) - return; // Failed to find valid spawn location, try again on next cycle - - var wave = GetNextWave(msg); - if (wave == null) - return; // Presence already spawned once, or not found - - var fieldCenter = GetOreFieldCenter(msg); - - DoBeams(fieldCenter); - - _spawning = true; - Task.Delay(SPAWN_DELAY).ContinueWith(t => - { - try - { - DoSpawning(wave, fieldCenter, spawnPos, msg.Node); - } - catch (Exception ex) - { - Logger.Exception(ex); - } - finally - { - _spawning = false; - } - }); - } - } - } -} 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/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index 45bff5d5b..987583c13 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs @@ -73,7 +73,7 @@ private TimeSpan GetRespawnTime() { if (IsBoss) { - return BossInfo.OnRespawn(RespawnTime); + return BossInfo.GetNextSpawnTime(RespawnTime); } return RespawnTime; } @@ -124,6 +124,10 @@ protected override void CreateMemberInZone() if (IsMaxSpawnCountReached) return; + if (IsBoss) + { + BossInfo.OnRespawn(); + } base.CreateMemberInZone(); } diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index aa10201f9..c14597e38 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -2,6 +2,7 @@ using Perpetuum.Players; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Intrusion; using System; @@ -46,7 +47,6 @@ public static NpcBossInfo GetBossInfoByFlockID(int flockid) } private readonly int _id; - private readonly int _flockid; private readonly double? _respawnNoiseFactor; private readonly long? _outpostEID; private readonly int? _stabilityPts; @@ -54,16 +54,19 @@ public static NpcBossInfo GetBossInfoByFlockID(int flockid) private readonly string _aggroMsg; private bool _speak; + public int FlockId { get; } + private bool IsOutpostBoss { get { return _outpostEID != null; } } private int StabilityPoints { get { return _stabilityPts ?? 0; } } private bool OverrideRelations { get; } public bool IsLootSplit { get; } + public bool IsDead { get; private set; } public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg) { _id = id; - _flockid = flockid; + FlockId = flockid; _respawnNoiseFactor = respawnNoiseFactor; IsLootSplit = lootSplit; _outpostEID = outpostEID; @@ -72,6 +75,7 @@ public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSpl _deathMsg = customDeathMsg; _aggroMsg = customAggroMsg; _speak = true; + IsDead = false; } /// @@ -93,6 +97,23 @@ public void OnAggro(Player aggressor, EventListenerService channel) HandleBossOutpostAggro(aggressor); } + // Timer to buffer excessive decrease frequency of messages from OnDamageTaken + private readonly TimeKeeper _time = new TimeKeeper(TimeSpan.FromSeconds(5)); + /// + /// Handle events to dispatch when the npc boss takes damage + /// + /// The npc Boss killed + /// Player damager + /// npc-event listener channel + public void OnDamageTaken(Npc npc, Player aggressor, EventListenerService channel) + { + if (_time.Expired) + { + channel.PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + _time.Reset(); + } + } + /// /// Handle any death behavior for this Boss NPC /// Includes sending a message, and affecting outpost's stability if set @@ -104,16 +125,26 @@ public void OnDeath(Npc npc, Unit killer, EventListenerService channel) { CommunicateDeath(killer, channel); HandleBossOutpostDeath(npc, killer, channel); + IsDead = true; + channel.PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + } + + /// + /// The boss lives again + /// + public void OnRespawn() + { + _speak = true; + IsDead = false; } /// - /// Apply any respawn timer modifiers, and reset any internal state on-respawn + /// Apply any respawn timer modifiers /// /// normal respawn time of npc /// modified respawn time of npc - public TimeSpan OnRespawn(TimeSpan respawnTime) + public TimeSpan GetNextSpawnTime(TimeSpan respawnTime) { - _speak = true; var factor = _respawnNoiseFactor ?? 0.0; return respawnTime.Multiply(FastRandom.NextDouble(1.0 - factor, 1.0 + factor)); } @@ -171,5 +202,26 @@ private static void SendMessage(Unit src, EventListenerService eventChannel, str Task.Run(() => eventChannel.PublishMessage(eventMessage)); } } + + 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; + } + } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 7369c6a95..5afe0c8f8 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -16,6 +16,7 @@ using Perpetuum.PathFinders; using Perpetuum.Players; using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.Looting; using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; @@ -748,6 +749,11 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) if (player == null) return; + if (IsBoss()) + { + BossInfo.OnDamageTaken(this, player, _eventChannel); + } + AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); } @@ -756,13 +762,12 @@ 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(); if (IsBoss()) { BossInfo.OnDeath(this, killer, _eventChannel); } + HandleNpcDeadAsync(zone, killer, tagger).ContinueWith((t) => base.OnDead(killer)).LogExceptions(); } private Task HandleNpcDeadAsync(IZone zone, Unit killer, Player tagger) diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs index 1fabedd30..2985a94db 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/INpcReinforcements.cs @@ -3,16 +3,16 @@ namespace Perpetuum.Zones.NpcSystem.Reinforcements { /// - /// Ore NPC Spawn - a list of presences that spawn at particular levels of field depletion + /// 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 of ore field mined + /// double of range [0.-1.] representing a percentage /// IOreNpcData - INpcReinforcementWave GetNextPresence(double percentageOfFieldConsumed); + INpcReinforcementWave GetNextPresence(double threshold); /// /// Does this INpcReinforcements have the provided Presence active? diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs index f50f2ef0e..b9a58722a 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcements.cs @@ -18,13 +18,13 @@ public NpcReinforcements(INpcReinforcementWave[] presences) /// /// Get the next unspawned presence in the list with the next greatest threshold /// - /// percentage expressed as [0.0-1.0] + /// percentage expressed as [0.0-1.0] /// INpcReinforcementWave or null - public INpcReinforcementWave GetNextPresence(double percentageOfFieldConsumed) + public INpcReinforcementWave GetNextPresence(double threshold) { - for (var i = 0; i < _presences.Length; i++) + for (var i = _presences.Length - 1; i >= 0; i--) { - if (_presences[i].Threshold > percentageOfFieldConsumed) + if (_presences[i].Threshold < threshold) { return _presences[i].Spawned ? null : _presences[i]; } @@ -50,7 +50,7 @@ public INpcReinforcementWave[] GetAllActiveWaves() public override string ToString() { var sb = new StringBuilder(); - sb.AppendLine("OreNPCSpawn {"); + sb.AppendLine("ReinforceSpawn {"); for (var i = 0; i < _presences.Length; i++) { sb.AppendLine(_presences[i].ToString()); diff --git a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs index 06c9e9cec..559532600 100644 --- a/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Reinforcements/NpcReinforcementsRepository.cs @@ -30,7 +30,13 @@ private static INpcReinforcementWave CreateFromRecord(IDataRecord record) public INpcReinforcements CreateNpcBossAddSpawn(NpcBossInfo npcBossInfo, int zoneId) { - throw new System.NotImplementedException(); //TODO new feature =) + 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/Terrains/Materials/Minerals/MineralLayer.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs index 613518a78..6418ce834 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralLayer.cs @@ -169,7 +169,7 @@ private bool IsNPCSpawningOre(MineralNode node) } // Timer to buffer excessive decrease messages on ore mining - private readonly TimeKeeper _time = new TimeKeeper(TimeSpan.FromSeconds(1)); + private readonly TimeKeeper _time = new TimeKeeper(TimeSpan.FromSeconds(5)); private void OnNodeDecrease(MineralNode node) { if (_time.Expired) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs index bec06c886..9180150d3 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Minerals/MineralNode.cs @@ -245,7 +245,7 @@ public override bool Equals(object obj) public bool Equals(MineralNode other) { - return other.Type == Type && other.Area == _area; + return other != null && ReferenceEquals(this, other) || other.Type == Type && other.Area == _area; } public override int GetHashCode() diff --git a/src/Perpetuum/Zones/ZoneExtensions.Npc.cs b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs index 218e63c4b..2ad2359b5 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Npc.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Npc.cs @@ -1,6 +1,6 @@ -using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.NpcSystem.Flocks; using Perpetuum.Zones.NpcSystem.Presences; -using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; using System; namespace Perpetuum.Zones From e9fc1c0dfcdcd09d5b0696c3533df5b6efdf6a6c Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 9 Dec 2020 00:18:40 -0500 Subject: [PATCH 230/457] Fix: ZoneEffectHandler null add/remove (#247) --- .../EventProcessors/EnvironmentalEffectHandler.cs | 5 +---- src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index ab135d4d2..7594a25a2 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -106,10 +106,7 @@ private void OnStateChange() if (!isSameEffect) { - if (_currentEffect != null) - { - _zone.ZoneEffectHandler.RemoveEffect(_currentEffect); - } + _zone.ZoneEffectHandler.RemoveEffect(_currentEffect); _zone.ZoneEffectHandler.AddEffect(nextEffect); _currentEffect = nextEffect; } diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs index 29fff6619..730996f5d 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs @@ -30,12 +30,16 @@ private void InitCollection() 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); } From 8838baaa644b60ae3e89bfb18fca951dfa66b31f Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 12 Dec 2020 22:24:33 -0500 Subject: [PATCH 231/457] increase freq of gametime updates (#253) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 95ee59d42..abff49268 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1720,7 +1720,7 @@ private void InitRelayManager() _builder.RegisterType().As().SingleInstance().OnActivated(e => { - e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(15))); + e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1.5))); }); // OPP: InterzoneNPCManager From df513382a1b4d8358b01ff89345324398a8e1d33 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 24 Dec 2020 21:33:09 -0500 Subject: [PATCH 232/457] Ewar falloff (#263) --- src/Perpetuum/Modules/ActiveModule.cs | 2 +- .../EffectModules/SensorDampenerModule.cs | 19 +++++++++++-------- .../Modules/EffectModules/WebberModule.cs | 10 ++++++++-- src/Perpetuum/Modules/SensorJammerModule.cs | 17 ++++++++++------- src/Perpetuum/Zones/NpcSystem/Creature.cs | 3 ++- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index beaf6435c..26c470da5 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -213,7 +213,7 @@ protected ActiveModule(bool ranged) : this(CategoryFlags.undefined,ranged) public double OptimalRange => optimalRange.Value; - protected double Falloff => falloff.Value; + public double Falloff => falloff.Value; public Lock Lock { diff --git a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs index 1e7670fa6..31dc3f9c7 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -33,12 +33,15 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override bool CanApplyEffect(Unit target) { - var sensorStrength = target.SensorStrength * FastRandom.NextDouble(); - sensorStrength = ModifyValueByOptimalRange(target, sensorStrength); - - if (sensorStrength < _ecmStrength.Value) - return true; - + var rangeMod = ModifyValueByOptimalRange(target, 1.0); + if (FastRandom.NextDouble() <= rangeMod) + { + var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble() * rangeMod; + if (targetSensorStrength < _ecmStrength.Value) + { + return true; + } + } OnError(ErrorCodes.AccuracyCheckFailed); return false; } @@ -51,8 +54,8 @@ protected override void OnApplyingEffect(Unit target) protected override void SetupEffect(EffectBuilder effectBuilder) { effectBuilder.SetType(EffectType.effect_sensor_supress).SetSource(ParentRobot) - .WithPropertyModifier(_effectSensorDampenerLockingRangeModifier.ToPropertyModifier()) - .WithPropertyModifier(_effectSensorDampenerLockingTimeModifier.ToPropertyModifier()); + .WithPropertyModifier(_effectSensorDampenerLockingRangeModifier.ToPropertyModifier()) + .WithPropertyModifier(_effectSensorDampenerLockingTimeModifier.ToPropertyModifier()); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/EffectModules/WebberModule.cs b/src/Perpetuum/Modules/EffectModules/WebberModule.cs index d3c9ecd54..7ebabc701 100644 --- a/src/Perpetuum/Modules/EffectModules/WebberModule.cs +++ b/src/Perpetuum/Modules/EffectModules/WebberModule.cs @@ -26,6 +26,12 @@ public override void AcceptVisitor(IEntityVisitor 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; @@ -51,8 +57,8 @@ protected override void SetupEffect(EffectBuilder effectBuilder) effectProperty.ResetToDefaultValue(); effectBuilder.SetType(EffectType.effect_demobilizer) - .SetSource(ParentRobot) - .WithPropertyModifier(effectProperty); + .SetSource(ParentRobot) + .WithPropertyModifier(effectProperty); } } diff --git a/src/Perpetuum/Modules/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index c0d1d898e..b2ab3efaa 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -31,14 +31,17 @@ protected override void OnAction() var unitLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); var robot = unitLock.Target.ThrowIfNotType(ErrorCodes.InvalidTarget); - var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble(); - targetSensorStrength = ModifyValueByOptimalRange(robot, targetSensorStrength); - - var success = targetSensorStrength < _ecmStrength.Value; - if (success) + var success = false; + var rangeMod = ModifyValueByOptimalRange(robot, 1.0); + if (FastRandom.NextDouble() <= rangeMod) { - robot.ResetLocks(); - robot.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); + var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble() * rangeMod; + if (targetSensorStrength < _ecmStrength.Value) + { + robot.ResetLocks(); + robot.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); + success = true; + } } var packet = new CombatLogPacket(CombatLogType.Jammer, robot,ParentRobot,this); diff --git a/src/Perpetuum/Zones/NpcSystem/Creature.cs b/src/Perpetuum/Zones/NpcSystem/Creature.cs index 7187db4d9..51055949e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Creature.cs +++ b/src/Perpetuum/Zones/NpcSystem/Creature.cs @@ -73,12 +73,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(); From 72dc3355900587638e37dd237f04e4f90cd095d1 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 24 Dec 2020 22:32:25 -0500 Subject: [PATCH 233/457] Ewar range logic fix (#264) --- src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs | 5 ++--- src/Perpetuum/Modules/SensorJammerModule.cs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs index 31dc3f9c7..ab3c0986e 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -33,10 +33,9 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override bool CanApplyEffect(Unit target) { - var rangeMod = ModifyValueByOptimalRange(target, 1.0); - if (FastRandom.NextDouble() <= rangeMod) + if (FastRandom.NextDouble() <= ModifyValueByOptimalRange(target, 1.0)) { - var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble() * rangeMod; + var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble(); if (targetSensorStrength < _ecmStrength.Value) { return true; diff --git a/src/Perpetuum/Modules/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index b2ab3efaa..1ad11e25f 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -32,10 +32,9 @@ protected override void OnAction() var robot = unitLock.Target.ThrowIfNotType(ErrorCodes.InvalidTarget); var success = false; - var rangeMod = ModifyValueByOptimalRange(robot, 1.0); - if (FastRandom.NextDouble() <= rangeMod) + if (FastRandom.NextDouble() <= ModifyValueByOptimalRange(robot, 1.0)) { - var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble() * rangeMod; + var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble(); if (targetSensorStrength < _ecmStrength.Value) { robot.ResetLocks(); From f9fc6e532a8378fdd7cec736ab773e6030f885ba Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 29 Dec 2020 17:38:35 -0500 Subject: [PATCH 234/457] Support for Elite modules feature (#267) Tricky --- src/Perpetuum/Robots/RobotComponent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Robots/RobotComponent.cs b/src/Perpetuum/Robots/RobotComponent.cs index 5cacbcba1..7b4ac8f7a 100644 --- a/src/Perpetuum/Robots/RobotComponent.cs +++ b/src/Perpetuum/Robots/RobotComponent.cs @@ -233,8 +233,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); From b2bbe3b257cf15f41f98772e772b2e848bddb74e Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 29 Dec 2020 17:39:08 -0500 Subject: [PATCH 235/457] Npc module use enchancements (#255) * closes #254 * closes #250, additional tuning for more conservative use of accum --- src/Perpetuum/Zones/NpcSystem/Turret.cs | 33 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 7aea8cde1..2e18349a5 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -28,7 +28,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private readonly IntervalTimer _timer; private readonly ActiveModule _module; @@ -100,16 +101,20 @@ public void Visit(WeaponModule module) } private const double ARMOR_REPAIR_THRESHOLD = 0.8; + private const double ARMOR_REPAIR_CORE_THRESHOLD = 0.35; 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); } - private const double SHIELD_ARMOR_THRESHOLD = 0.3; + private const double SHIELD_ARMOR_THRESHOLD = 0.35; public void Visit(ShieldGeneratorModule module) { @@ -119,7 +124,7 @@ public void Visit(ShieldGeneratorModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } - private const double SENSOR_JAMMER_CORE_THRESHOLD = 0.5; + private const double SENSOR_JAMMER_CORE_THRESHOLD = 0.55; public void Visit(SensorJammerModule module) { @@ -134,7 +139,7 @@ public void Visit(SensorJammerModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } - private const double SENSOR_DAMPENER_CORE_THRESHOLD = 0.5; + private const double SENSOR_DAMPENER_CORE_THRESHOLD = 0.55; public void Visit(SensorDampenerModule module) { @@ -149,7 +154,7 @@ public void Visit(SensorDampenerModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } - private const double WEBBER_CORE_THRESHOLD = 0.5; + private const double WEBBER_CORE_THRESHOLD = 0.55; public void Visit(WebberModule module) { @@ -164,10 +169,13 @@ public void Visit(WebberModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } - private const double BLOBBER_CORE_THRESHOLD = 0.5; + private const double BLOBBER_CORE_THRESHOLD = 0.55; public void Visit(BlobEmissionModulatorModule module) { + if (module.ParentRobot.Zone.Configuration.Protected) + return; + if (module.ParentRobot.CorePercentage < BLOBBER_CORE_THRESHOLD) return; @@ -180,7 +188,7 @@ public void Visit(BlobEmissionModulatorModule module) } - private const double BLINDER_CORE_THRESHOLD = 0.5; + private const double BLINDER_CORE_THRESHOLD = 0.55; public void Visit(TargetBlinderModule module) { @@ -196,7 +204,7 @@ public void Visit(TargetBlinderModule module) } - private const double ENERGY_NEUTRALIZER_CORE_THRESHOLD = 0.5; + private const double ENERGY_NEUTRALIZER_CORE_THRESHOLD = 0.55; public void Visit(EnergyNeutralizerModule module) { @@ -264,6 +272,15 @@ public void Visit(ArmorHardenerModule module) } } + private const double ENERGY_INJECTOR_THRESHOLD = 0.65; + + public void Visit(CoreBoosterModule module) + { + if (module.ParentRobot.CorePercentage > ENERGY_INJECTOR_THRESHOLD) + return; + + module.State.SwitchTo(ModuleStateType.Oneshot); + } } From 6387240e5a2b803118f8db735320ae33de000c18 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 31 Dec 2020 17:14:18 -0500 Subject: [PATCH 236/457] Stationary npcs (#256) =) --- .../Collections/WeightedCollection.cs | 56 +++ src/Perpetuum/GenericTypeExtensions.cs | 1 - .../EffectModules/SensorDampenerModule.cs | 10 +- src/Perpetuum/Modules/SensorJammerModule.cs | 14 +- src/Perpetuum/Perpetuum.csproj | 1 + .../NpcReinforcementSpawner.cs | 4 +- src/Perpetuum/Units/Unit.Visibility.cs | 2 +- src/Perpetuum/Units/Unit.cs | 5 + src/Perpetuum/Zones/NpcSystem/Npc.cs | 326 +++++++++++++++--- .../NpcSystem/Presences/DynamicPresence.cs | 2 +- src/Perpetuum/Zones/NpcSystem/Turret.cs | 2 +- 11 files changed, 364 insertions(+), 59 deletions(-) create mode 100644 src/Perpetuum/Collections/WeightedCollection.cs diff --git a/src/Perpetuum/Collections/WeightedCollection.cs b/src/Perpetuum/Collections/WeightedCollection.cs new file mode 100644 index 000000000..32d7933be --- /dev/null +++ b/src/Perpetuum/Collections/WeightedCollection.cs @@ -0,0 +1,56 @@ +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; + + var weightTarget = FastRandom.NextInt(_sumWeights); + var current = 0; + var iterator = _list.GetEnumerator(); + while (current <= weightTarget && iterator.MoveNext()) + { + current += iterator.Current.Weight; + } + 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/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/Modules/EffectModules/SensorDampenerModule.cs b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs index ab3c0986e..aa1e5c44f 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -33,13 +33,11 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override bool CanApplyEffect(Unit target) { - if (FastRandom.NextDouble() <= ModifyValueByOptimalRange(target, 1.0)) + var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble(); + var rangedEcmStrength = ModifyValueByOptimalRange(target, _ecmStrength.Value); + if (targetSensorStrength < rangedEcmStrength) { - var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble(); - if (targetSensorStrength < _ecmStrength.Value) - { - return true; - } + return true; } OnError(ErrorCodes.AccuracyCheckFailed); return false; diff --git a/src/Perpetuum/Modules/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index 1ad11e25f..27791f5f9 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -32,15 +32,13 @@ protected override void OnAction() var robot = unitLock.Target.ThrowIfNotType(ErrorCodes.InvalidTarget); var success = false; - if (FastRandom.NextDouble() <= ModifyValueByOptimalRange(robot, 1.0)) + var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble(); + var rangedEcmStrength = ModifyValueByOptimalRange(robot, _ecmStrength.Value); + if (targetSensorStrength < rangedEcmStrength) { - var targetSensorStrength = robot.SensorStrength * FastRandom.NextDouble(); - if (targetSensorStrength < _ecmStrength.Value) - { - robot.ResetLocks(); - robot.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); - success = true; - } + robot.ResetLocks(); + robot.AddThreat(ParentRobot, new Threat(ThreatType.Ewar, Threat.SENSOR_DAMPENER)); + success = true; } var packet = new CombatLogPacket(CombatLogType.Jammer, robot,ParentRobot,this); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 9b8904032..b21a17c18 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -153,6 +153,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs index f003b0e92..7a2b92e47 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -14,8 +14,8 @@ namespace Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers { public class NpcReinforcementSpawner : NpcSpawnEventHandler { - protected override TimeSpan SPAWN_DELAY { get { return TimeSpan.FromSeconds(10); } } - protected override TimeSpan SPAWN_LIFETIME { get { return TimeSpan.FromMinutes(15); } } + 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 10; } } private readonly IDictionary _reinforcementsByNpc = new Dictionary(); diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index 4b2025e47..7ce73ab54 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -20,7 +20,7 @@ partial class Unit { private ImmutableDictionary _visibleUnits = ImmutableDictionary.Empty; - protected IReadOnlyCollection GetVisibleUnits() + public IReadOnlyCollection GetVisibleUnits() { return _visibleUnits.Values.ToArray(); } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 3b90ba79b..71f97eaa9 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -1032,6 +1032,11 @@ public double Speed } } + public double MaxSpeed + { + get { return _speedMax.Value; } + } + public TimeSpan CoreRechargeTime => TimeSpan.FromSeconds(_coreRechargeTime.Value); private void InitUnitProperties() diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 5afe0c8f8..278d6e05e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -16,7 +16,6 @@ using Perpetuum.PathFinders; using Perpetuum.Players; using Perpetuum.Services.EventServices; -using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.Looting; using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; @@ -29,6 +28,7 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Movements; using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.Terrains; namespace Perpetuum.Zones.NpcSystem { @@ -84,7 +84,7 @@ protected virtual void ToAggressorAI() [Conditional("DEBUG")] protected void WriteLog(string message) { -// Logger.InfoFormat("NpcAI: {0}",message); + //Logger.DebugInfo($"NpcAI: {message}"); } } @@ -92,9 +92,7 @@ public class IdleAI : NpcAI { private RandomMovement _movement; - public IdleAI(Npc npc) : base(npc) - { - } + public IdleAI(Npc npc) : base(npc) { } public override void Enter() { @@ -123,14 +121,218 @@ public override void Update(TimeSpan time) } } + public class StationaryIdleAI : NpcAI + { + public StationaryIdleAI(Npc npc) : base(npc) { } + + public override void Enter() + { + npc.StopAllModules(); + npc.ResetLocks(); + base.Enter(); + } + + public override void Update(TimeSpan time) + { + if (!npc.ThreatManager.Hostiles.IsEmpty) + { + ToAggressorAI(); + } + } + + protected override void ToHomeAI() { } + + protected override void ToAggressorAI() + { + if (npc.Behavior.Type == NpcBehaviorType.Passive) + return; + + npc.AI.Push(new StationaryCombatAI(npc)); + } + } + + public class StationaryCombatAI : CombatAI + { + private enum PrimaryLockStrategy + { + Random, + Hostile, + Closest, + OptimalRange + } + + private class PrimaryLockSelectionStrategySelector + { + private readonly WeightedCollection _selection; + public PrimaryLockSelectionStrategySelector() + { + _selection = new WeightedCollection(); + _selection.Add(PrimaryLockStrategy.Hostile, 1); + _selection.Add(PrimaryLockStrategy.Closest, 2); + _selection.Add(PrimaryLockStrategy.OptimalRange, 3); + _selection.Add(PrimaryLockStrategy.Random, 10); + } + public PrimaryLockStrategy GetStrategy() + { + return _selection.GetRandom(); + } + } + + private readonly IntervalTimer _updateFrequency = new IntervalTimer(500); + private readonly IntervalTimer _primarySelectTimer = new IntervalTimer(0); + private readonly PrimaryLockSelectionStrategySelector _stratSelector = new PrimaryLockSelectionStrategySelector(); + public StationaryCombatAI(Npc npc) : base(npc) { } + + public override void Update(TimeSpan time) + { + FindHostiles(time); + UpdateHostiles(time); + UpdatePrimaryTarget(time); + RunModules(time); + } + + private void FindHostiles(TimeSpan time) + { + _updateFrequency.Update(time); + if (_updateFrequency.Passed) + { + _updateFrequency.Reset(); + npc.LookingForHostiles(); + } + } + + private void UpdatePrimaryTarget(TimeSpan time) + { + _primarySelectTimer.Update(time); + if (_primarySelectTimer.Passed) + { + var success = SelectPrimaryTarget(); + SetPrimaryUpdateDelay(success); + } + } + + private UnitLock[] GetValidLocks() + { + return npc.GetLocks().Select(l => (UnitLock)l).Where(u => IsLockValidTarget(u) && !u.Primary).ToArray(); + } + + private bool SelectPrimaryTarget() + { + var validLocks = GetValidLocks(); + if (validLocks.Length < 1) + return false; + + var strategy = _stratSelector.GetStrategy(); + switch (strategy) + { + case PrimaryLockStrategy.Hostile: + PrimaryMostHated(validLocks); + break; + case PrimaryLockStrategy.Closest: + PrimaryClosest(validLocks); + break; + case PrimaryLockStrategy.OptimalRange: + PrimaryRandomWithinOptimal(validLocks); + break; + case PrimaryLockStrategy.Random: + default: + PrimaryRandom(validLocks); + break; + } + return validLocks.Any(l => l.Primary); + } + + protected override void SetLockForHostile(Hostile hostile) + { + if (npc.GetPrimaryLock() == null) + { + base.SetLockForHostile(hostile); + return; + } + + var l = npc.GetLockByUnit(hostile.unit); + if (l == null) + { + if (TryMakeFreeLockSlotFor(hostile)) + npc.AddLock(hostile.unit, false); + } + } + + private bool IsLockValidTarget(UnitLock unitLock) + { + if (unitLock == null || unitLock.State != LockState.Locked) + return false; + + var visibility = npc.GetVisibility(unitLock.Target); + if (visibility == null) + return false; + + var r = visibility.GetLineOfSight(false); + if (r != null) + { + if (r.hit && (r.blockingFlags & BlockingFlags.Plant) == 0) + return false; + } + return unitLock.Target.GetDistance(npc) < npc.MaxCombatRange; + } + + private bool PrimaryMostHated(UnitLock[] locks) + { + var hostiles = npc.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(mostHostileLock); + } + + private bool PrimaryRandomWithinOptimal(UnitLock[] locks) + { + return TrySetPrimaryLock(locks.Where(k => k.Target.GetDistance(npc) < npc.BestCombatRange).RandomElement()); + } + + private bool PrimaryClosest(UnitLock[] locks) + { + return TrySetPrimaryLock(locks.OrderBy(u => u.Target.GetDistance(npc)).First()); + } + + private bool PrimaryRandom(UnitLock[] locks) + { + return TrySetPrimaryLock(locks.RandomElement()); + } + + private void SetPrimaryUpdateDelay(bool newPrimary) + { + if (newPrimary) + { + _primarySelectTimer.Interval = FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + } + else if (GetValidLocks().Length > 0) + { + _primarySelectTimer.Interval = TimeSpan.FromSeconds(1); + } + else if (npc.GetLocks().Count > 0) + { + _primarySelectTimer.Interval = TimeSpan.FromSeconds(1.5); + } + else + { + _primarySelectTimer.Interval = TimeSpan.FromSeconds(3.5); + } + } + + private bool TrySetPrimaryLock(Lock l) + { + if (l == null) return false; + npc.SetPrimaryLock(l); + return true; + } + } + public class CombatAI : NpcAI { private List _moduleActivators; private readonly IntervalTimer _processHostilesTimer = new IntervalTimer(1500); - protected CombatAI(Npc npc) : base(npc) - { - } + public CombatAI(Npc npc) : base(npc) { } public override void Enter() { @@ -138,7 +340,7 @@ public override void Enter() base.Enter(); } - public override void Update(TimeSpan time) + protected void UpdateHostiles(TimeSpan time) { _processHostilesTimer.Update(time); if (_processHostilesTimer.Passed) @@ -146,14 +348,23 @@ public override void Update(TimeSpan time) _processHostilesTimer.Reset(); ProcessHostiles(); } - + } + + protected void RunModules(TimeSpan time) + { foreach (var activator in _moduleActivators) { activator.Update(time); } } - private bool IsAttackable(Hostile hostile) + public override void Update(TimeSpan time) + { + UpdateHostiles(time); + RunModules(time); + } + + protected bool IsAttackable(Hostile hostile) { if (!hostile.unit.InZone) return false; @@ -183,7 +394,24 @@ private bool IsAttackable(Hostile hostile) return true; } - private void ProcessHostiles() + protected virtual void SetLockForHostile(Hostile hostile) + { + 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); + } + } + + protected virtual void ProcessHostiles() { var hostileEnumerator = npc.ThreatManager.Hostiles.GetEnumerator(); while (hostileEnumerator.MoveNext()) @@ -199,23 +427,11 @@ private void ProcessHostiles() 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); - } + SetLockForHostile(hostile); } } - private bool TryMakeFreeLockSlotFor(Hostile hostile) + protected bool TryMakeFreeLockSlotFor(Hostile hostile) { if (npc.HasFreeLockSlot) return true; @@ -232,16 +448,19 @@ private bool TryMakeFreeLockSlotFor(Hostile hostile) public class HomingAI : CombatAI { private PathMovement _movement; + private readonly double _maxReturnHomeRadius; + private readonly PathFinder _pathFinder; public HomingAI(Npc npc) : base(npc) { + _maxReturnHomeRadius = (npc.HomeRange * 0.4).Clamp(1, 20); + _pathFinder = new AStarFinder(Heuristic.Manhattan, npc.IsWalkable); } public override void Enter() { - var pathFinder = new AStarFinder(Heuristic.Manhattan, npc.IsWalkable); - var randomHome = npc.HomePosition.GetRandomPositionInRange2D(1, npc.HomeRange * 0.4); - pathFinder.FindPathAsync(npc.CurrentPosition, randomHome).ContinueWith(t => + var randomHome = npc.HomePosition.GetRandomPositionInRange2D(1, _maxReturnHomeRadius); + _pathFinder.FindPathAsync(npc.CurrentPosition, randomHome).ContinueWith(t => { var path = t.Result; if (path == null) @@ -296,7 +515,6 @@ protected override void ToAggressorAI() } } - public class AggressorAI : CombatAI { public AggressorAI(Npc npc) : base(npc) @@ -539,7 +757,6 @@ public static NpcBehavior Create(NpcBehaviorType type) return new NeutralBehavior(); case NpcBehaviorType.Aggressive: return new AggressiveBehavior(); - case NpcBehaviorType.Passive: return new PassiveBehavior(); default: @@ -574,13 +791,16 @@ 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 Lazy _maxCombatRange; + private Lazy _optimalCombatRange; private TimeSpan _lastHelpCalled; private readonly EventListenerService _eventChannel; private readonly IPseudoThreatManager _pseudoThreatManager; public Npc(TagHelper tagHelper, EventListenerService eventChannel) { + _maxCombatRange = new Lazy(CalculateMaxCombatRange); + _optimalCombatRange = new Lazy(CalculateCombatRange); _eventChannel = eventChannel; _tagHelper = tagHelper; _threatManager = new ThreatManager(); @@ -629,7 +849,17 @@ public bool IsInHomeRange public int BestCombatRange { - get { return (int) (_bestCombatRange ?? (_bestCombatRange = CalculateCombatRange())); } + get { return _optimalCombatRange.Value; } + } + + public int MaxCombatRange + { + get { return _maxCombatRange.Value; } + } + + public bool IsStationary + { + get { return MaxSpeed.IsZero(); } } public void Tag(Player tagger,TimeSpan duration) @@ -699,7 +929,8 @@ protected override void OnPropertyChanged(ItemProperty property) { case AggregateField.locking_range: { - _bestCombatRange = null; + _optimalCombatRange = new Lazy(CalculateCombatRange); + _maxCombatRange = new Lazy(CalculateMaxCombatRange); break; } case AggregateField.armor_current: @@ -911,7 +1142,7 @@ protected override void OnTileChanged() LookingForHostiles(); } - private void LookingForHostiles() + public void LookingForHostiles() { foreach (var visibility in GetVisibleUnits()) { @@ -936,7 +1167,14 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) base.OnEnterZone(zone, enterType); - AI.Push(new IdleAI(this)); + if (IsStationary) + { + AI.Push(new StationaryIdleAI(this)); + } + else + { + AI.Push(new IdleAI(this)); + } } public override string InfoString @@ -962,7 +1200,7 @@ public override string InfoString private bool IsInAggroRange(Unit target) { - return IsInRangeOf3D(target, AGGRO_RANGE); + return this.IsStationary || IsInRangeOf3D(target, AGGRO_RANGE); } protected override void OnUnitLockStateChanged(Lock @lock) @@ -983,7 +1221,7 @@ protected override void OnUnitLockStateChanged(Lock @lock) protected override void OnUnitTileChanged(Unit target) { - AddBodyPullThreat(target); + AddBodyPullThreat(target); } internal override bool IsHostile(Player player) @@ -1086,6 +1324,16 @@ private int CalculateCombatRange() 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; + } + private const double BEST_COMBAT_RANGE_MODIFIER = 0.9; private class BodyPullThreatHelper : IEntityVisitor,IEntityVisitor @@ -1105,7 +1353,7 @@ public void Visit(Player player) if (player.HasTeleportSicknessEffect) return; - if (!_npc.ThreatManager.Hostiles.IsEmpty) + if (_npc.ThreatManager.Hostiles.Any(h => h.unit.Eid == player.Eid)) return; if (!_npc.IsInAggroRange(player)) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs index 44e651da7..fbbf70db1 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs @@ -37,7 +37,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.AI.Current is CombatAI); if (x) ResetDynamicDespawnTimer(); diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 2e18349a5..92282157c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -100,7 +100,7 @@ public void Visit(WeaponModule module) TryActiveModule(result,primaryLock); } - private const double ARMOR_REPAIR_THRESHOLD = 0.8; + private const double ARMOR_REPAIR_THRESHOLD = 0.95; private const double ARMOR_REPAIR_CORE_THRESHOLD = 0.35; public void Visit(ArmorRepairModule module) From 21106b1e9444af4903cb02e2c1e79cf6aa9bbfca Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 14 Jan 2021 20:15:41 -0500 Subject: [PATCH 237/457] PMCQ/session disposal/reinit fix (#273) * block on PMCQ disposal and session stop/cleanup * longer timeout, return if wait succeeded * encapsulate end of lifecycle functions into one, logging just in case --- src/Perpetuum/Players/Player.cs | 14 ++++--------- src/Perpetuum/Players/PlayerMoveChecker.cs | 23 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 47730220f..c35813ccd 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -35,7 +35,6 @@ using Perpetuum.Zones.CombatLogs; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.Effects.ZoneEffects; using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Locking; @@ -267,15 +266,10 @@ protected override void OnRemovedFromZone(IZone zone) Session.SendPacket(ExitPacketBuilder); zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); - Task.Run(() => - { - _check.Stop(); - _check.Dispose(); - }).ContinueWith(t => - { - if (!States.LocalTeleport) - Session.Stop(); - }); + _check.StopAndDispose(); + + if (!States.LocalTeleport) + Session.Stop(); base.OnRemovedFromZone(zone); } diff --git a/src/Perpetuum/Players/PlayerMoveChecker.cs b/src/Perpetuum/Players/PlayerMoveChecker.cs index 54f034af2..cbe8a64a1 100644 --- a/src/Perpetuum/Players/PlayerMoveChecker.cs +++ b/src/Perpetuum/Players/PlayerMoveChecker.cs @@ -11,6 +11,7 @@ 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; @@ -48,7 +49,24 @@ private void Start() _task.Start(); } - public void Stop() + 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(); @@ -56,7 +74,8 @@ public void Stop() if (!IsCompleted) _movesToReview.CompleteAdding(); - _task.Wait(); + _movesToReview.Clear(); + return _task.Wait(MAX_TIMEOUT); } public void EnqueueMove(Position target) From 7665a1e6b88b87f7e6437dfc0844a7cf26662e42 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 14 Jan 2021 20:16:33 -0500 Subject: [PATCH 238/457] null when updating zone with new rules (#270) --- src/Perpetuum/Zones/Terrains/NatureCube.cs | 2 ++ 1 file changed, 2 insertions(+) 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) { From f0a04b622198ca903f66558228d09c41cfc9a38e Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 14 Jan 2021 20:17:17 -0500 Subject: [PATCH 239/457] command to lock out accidental layer edits (#272) --- .../Zone/ZoneCleanObstacleBlocking.cs | 1 + .../Zone/ZoneClearLayer.cs | 1 + .../Zone/ZoneCreateIsland.cs | 1 + .../Zone/ZoneDrawRamp.cs | 1 + .../Zone/ZoneTerraformTest.cs | 1 + .../ChatCommands/AdminCommandHandlers.cs | 49 ++++++++++++++++++- src/Perpetuum/Zones/IZone.cs | 1 + src/Perpetuum/Zones/Zone.cs | 2 + src/Perpetuum/Zones/ZoneSession.cs | 1 + 9 files changed, 56 insertions(+), 2 deletions(-) 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..37b6efcd0 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs @@ -7,6 +7,7 @@ public class ZoneClearLayer : IRequestHandler { public void HandleRequest(IZoneRequest request) { + request.Zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var layerName = request.Data.GetOrDefault(k.layerName); switch (layerName) 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/ZoneDrawRamp.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs index 603817db4..8ac62e04f 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs @@ -28,6 +28,7 @@ public void HandleRequest(IZoneRequest request) fullBlend = fullBlend.Clamp(); var zone = request.Zone; + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); Player player; if (zone.TryGetPlayer(request.Session.Character, out player)) 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/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 508b6190f..fb47b8845 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -50,6 +50,7 @@ private static void ZoneSetTilesControl(AdminCommandData data, TerrainControlFla var character = data.Request.Session.Character; var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); var player = zone.GetPlayer(character.ActiveRobotEid); + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var lockedtiles = player.GetLocks(); @@ -69,6 +70,40 @@ private static void ZoneSetTilesControl(AdminCommandData data, TerrainControlFla } 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; + var zoneId = -1; + if (data.Command.Args.IsNullOrEmpty()) + { + var 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); + } + err = !data.Request.Session.ZoneMgr.ContainsZone(zoneId); + if (err) + { + SendMessageToAll(data, "Zone not found"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + + var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + zone.IsLayerEditLocked = toLock; + SendMessageToAll(data, $"All layers on zone {zoneId} {(toLock ? "LOCKED" : "UNLOCKED")}!"); + } public static void Unknown(AdminCommandData data) { SendMessageToAll(data, $"Unknown command: {data.Command.Name}"); @@ -761,7 +796,7 @@ public static void ZoneAddBlockingToLockedTiles(AdminCommandData data) var character = data.Request.Session.Character; var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); var player = zone.GetPlayer(character.ActiveRobotEid); - + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var lockedtiles = player.GetLocks(); using (new TerrainUpdateMonitor(zone)) @@ -785,7 +820,7 @@ public static void ZoneRemoveBlockingToLockedTiles(AdminCommandData data) var character = data.Request.Session.Character; var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); var player = zone.GetPlayer(character.ActiveRobotEid); - + zone.IsLayerEditLocked.ThrowIfTrue(ErrorCodes.TileTerraformProtected); var lockedtiles = player.GetLocks(); using (new TerrainUpdateMonitor(zone)) @@ -951,6 +986,16 @@ public static void ZoneCreateGarden(AdminCommandData data) HandleLocalRequest(data, cmd); SendMessageToAll(data, $"Garden Created! "); } + [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) { diff --git a/src/Perpetuum/Zones/IZone.cs b/src/Perpetuum/Zones/IZone.cs index 8c9cf76e2..a2553a832 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -25,6 +25,7 @@ namespace Perpetuum.Zones public interface IZone { [NotNull] ZoneConfiguration Configuration { get; } + bool IsLayerEditLocked { get; set; } int Id { get; } Size Size { get; } diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index f7b06f3d9..07451e3bb 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -82,9 +82,11 @@ public abstract class Zone : Threading.Process.Process, IZone public TcpListener Listener { get; set; } 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; diff --git a/src/Perpetuum/Zones/ZoneSession.cs b/src/Perpetuum/Zones/ZoneSession.cs index c2f6ad90b..132b88c33 100644 --- a/src/Perpetuum/Zones/ZoneSession.cs +++ b/src/Perpetuum/Zones/ZoneSession.cs @@ -695,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)) { From 10f2f5e4f12ee505a41132ca6fb1cd53e291d4d8 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 24 Jan 2021 00:11:23 -0500 Subject: [PATCH 240/457] groundtype controls, draw current state, clear groundtype (#274) --- .../Zone/StatsMapDrawing/ZoneDrawStatMap.cs | 18 +++++ .../Zone/ZoneClearLayer.cs | 7 ++ src/Perpetuum/Keywords.cs | 1 + .../Channels/ChatCommands/AdminCommandData.cs | 2 +- .../ChatCommands/AdminCommandHandlers.cs | 75 ++++++++++++++----- src/Perpetuum/Zones/Terrains/GroundType.cs | 1 + .../Terrains/Materials/Plants/PlantInfo.cs | 5 ++ 7 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 46a1cce89..2e529ea3d 100644 --- a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs +++ b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs @@ -64,6 +64,7 @@ public ZoneDrawStatMap(IFileSystem fileSystem,SaveBitmapHelper saveBitmapHelper, RegisterCreator("displayspots", DisplaySpots); RegisterCreator("worstspots",DrawWorstSpotsMap); RegisterCreator("alltargets", DrawAllTargetsOnZone); + RegisterCreator(k.groundType, CreateGroundTypeMap); } private void RegisterCreator(string type,Func bitmapFactory) @@ -641,6 +642,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/ZoneClearLayer.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs index 37b6efcd0..175a3e7bf 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs @@ -12,6 +12,13 @@ public void HandleRequest(IZoneRequest request) 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) => { diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index dd2967afa..03c237624 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -429,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"; diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs index d2410f926..03a44927d 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandData.cs @@ -14,7 +14,7 @@ public class CommandArgs public CommandArgs(string[] commandArray) { Name = commandArray[0].Substring(1).ToLower().Trim(); - Args = commandArray.Skip(1).ToArray(); + Args = commandArray.Skip(1).Select(s => s.Trim()).ToArray(); } } diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index fb47b8845..54ebe8431 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -14,7 +14,7 @@ namespace Perpetuum.Services.Channels.ChatCommands { - public class ChatCommand: Attribute + public class ChatCommand : Attribute { public string Command { get; private set; } public ChatCommand(string command) @@ -93,17 +93,19 @@ private static void LockOrUnlockZoneLayers(AdminCommandData data, bool toLock) SendMessageToAll(data, "Error parsing args"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - err = !data.Request.Session.ZoneMgr.ContainsZone(zoneId); - if (err) - { - SendMessageToAll(data, "Zone not found"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } - + CheckZoneId(data, zoneId); var 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 Unknown(AdminCommandData data) { SendMessageToAll(data, $"Unknown command: {data.Command.Name}"); @@ -301,13 +303,24 @@ public static void SetVisibility(AdminCommandData data) [ChatCommand("ZoneDrawStatMap")] public static void ZoneDrawStatMap(AdminCommandData data) { - //TODO check args, maybe allow zone id, fallback to sender zone - Dictionary dictionary = new Dictionary() + var zoneId = data.Sender.ZoneId ?? -1; + if (data.Command.Args.Length < 1) + { + SendMessageToAll(data, $"Missing 'type' arg"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + var type = data.Command.Args[0]; + if (data.Command.Args.Length > 1) + { + if (!int.TryParse(data.Command.Args[1], out zoneId)) { - { "type", data.Command.Args[0] } - }; - - string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + SendMessageToAll(data, $"Bad 'zoneId' arg"); + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + } + CheckZoneId(data, zoneId); + var dictionary = new Dictionary() { { k.type, type } }; + string cmd = string.Format("zoneDrawStatMap:zone_{0}:{1}", zoneId, GenxyConverter.Serialize(dictionary)); HandleLocalRequest(data, cmd); } [ChatCommand("ListAllPlayersInZone")] @@ -494,11 +507,7 @@ public static void SetWeather(AdminCommandData data) { err = !int.TryParse(data.Command.Args[2], out zoneId); } - if (!data.Request.Session.ZoneMgr.ContainsZone(zoneId)) - { - SendMessageToAll(data, $"Bad or missing zone id"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } + CheckZoneId(data, zoneId); var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); var current = zone.Weather.GetCurrentWeather(); var weather = new WeatherInfo(current.Next, weatherInt.Min(255), TimeSpan.FromSeconds(seconds)); @@ -514,11 +523,12 @@ public static void GetWeather(AdminCommandData data) { err = !int.TryParse(data.Command.Args[0], out zoneId); } - if (!data.Request.Session.ZoneMgr.ContainsZone(zoneId) || err) + if (err) { SendMessageToAll(data, $"Bad or missing zone id"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } + CheckZoneId(data, zoneId); var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather().ToString()}"); } @@ -986,6 +996,31 @@ public static void ZoneCreateGarden(AdminCommandData data) HandleLocalRequest(data, cmd); SendMessageToAll(data, $"Garden Created! "); } + [ChatCommand("ZoneClearGroundType")] + public static void ZoneClearGroundType(AdminCommandData data) + { + if (!IsDevModeEnabled(data)) + return; + + bool err = false; + + var 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); + var dictionary = new Dictionary() { { k.layerName, k.groundType } }; + var 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("ZoneLockLayers")] public static void ZoneLockLayers(AdminCommandData data) { 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/Materials/Plants/PlantInfo.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs index 733ca0613..233b2b93a 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs @@ -23,6 +23,11 @@ public void Clear() material = 0; } + public void ClearGroundType() + { + groundType = GroundType.undefined; + } + public void SetPlant(byte newState, PlantType newPlantType) { state = newState; From 7d1583366b9f9b2af4a9098317ddfcbbb5e91679 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 4 Feb 2021 22:27:40 -0500 Subject: [PATCH 241/457] Production cost: by category and/or tech and/or techtype (#275) --- .../PerpetuumBootstrapper.cs | 1 + .../Facilities/ProductionFacility.cs | 5 +- .../ProductionEngine/IProductionDataAccess.cs | 3 +- .../ProductionEngine/ProductionCost.cs | 66 ++++++++++++++++++- .../ProductionEngine/ProductionDataAccess.cs | 52 +++------------ .../ProductionDataAccessExtensions.cs | 20 +++--- 6 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index abff49268..c567ca56e 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1641,6 +1641,7 @@ private void InitRelayManager() _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType().As(); _builder.RegisterType().OnActivated(e => { e.Instance.Init(); diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs index ee2b661b1..df17f0b28 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs @@ -226,9 +226,8 @@ private int GetStandingPoints(Character character) //Overload method for price/sec public virtual double GetPricePerSecond(int targetDefinition) { - var mod = Math.Max(1, ProductionDataAccess.GetProductionPriceModifier(targetDefinition)); - mod = Math.Min(10, mod); - return GetPricePerSecond() * mod; + var mod = ProductionDataAccess.GetProductionPriceModifier(targetDefinition); + return GetPricePerSecond() * mod.Clamp(1, 10); } public virtual double GetPricePerSecond() diff --git a/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs b/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs index 1d4209ce6..b7e273ba6 100644 --- a/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs +++ b/src/Perpetuum/Services/ProductionEngine/IProductionDataAccess.cs @@ -11,8 +11,7 @@ public interface IProductionDataAccess IDictionary ResearchLevels { get; } ILookup ProductionComponents { get; } IDictionary ProductionDurations { get; } - IDictionary ProductionCostByCategory { get; } - IDictionary ProductionCostByTechLevel { get; } + IDictionary ProductionCost { get; } IDictionary CalibrationDefaults { get; } ProductionDecalibration GetDecalibration(int targetDefinition); diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs b/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs index 9e1fa664c..93d25613b 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionCost.cs @@ -1,4 +1,9 @@ - +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; + namespace Perpetuum.Services.ProductionEngine { public class ProductionCost @@ -8,4 +13,61 @@ public class ProductionCost 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 60b63db8a..497e812af 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccess.cs @@ -16,14 +16,14 @@ public class ProductionDataAccess : IProductionDataAccess private IDictionary _researchlevels; private ILookup _productionComponents; private IDictionary _productionDurations; - private IDictionary _productionCostByCategory; - private IDictionary _productionCostByTechLevel; 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() @@ -68,46 +68,11 @@ public void Init() return level; }, ItemResearchLevelFilter); - _productionCostByCategory = Database.CreateCache("productioncost", k.category, r => + ProductionCost = new Dictionary(); + foreach (var ed in EntityDefault.All) { - 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; - }, ProductionCostCategoryFilter); - - _productionCostByTechLevel = Database.CreateCache("productioncost", k.tierLevel, 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; - }, ProductionCostTechFilter); - } - - public bool ProductionCostCategoryFilter(IDataRecord record) - { - long? categoryFlag = record.GetValue(k.category); - if (categoryFlag == null) - return false; - CategoryFlags flag = (CategoryFlags)categoryFlag; - return flag.IsCategoryExists(); - } - - public bool ProductionCostTechFilter(IDataRecord record) - { - int? level = record.GetValue(k.tierLevel); - if (level == null) - return false; - return level > 0; + ProductionCost.Add(ed.Definition, _productionCostReader.GetProductionCostModByED(ed)); + } } public bool ItemResearchLevelFilter(IDataRecord record) @@ -135,9 +100,8 @@ public bool ItemResearchLevelFilter(IDataRecord record) public IDictionary ResearchLevels => _researchlevels; public ILookup ProductionComponents => _productionComponents; public IDictionary ProductionDurations => _productionDurations; - public IDictionary ProductionCostByCategory => _productionCostByCategory; - public IDictionary ProductionCostByTechLevel => _productionCostByTechLevel; 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 0bb609879..89c2bd621 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionDataAccessExtensions.cs @@ -71,25 +71,25 @@ public static double GetProductionDuration(this IProductionDataAccess dataAccess return 1.0; } + /// + /// 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) { + var modifier = 1.0; if (!EntityDefault.TryGet(targetDefinition, out EntityDefault ed)) { Logger.Error("definition was not found: " + targetDefinition); - return 1.0; + return modifier; } - var modifier = 1.0; - - if (dataAccess.ProductionCostByCategory.TryGetValue(ed.CategoryFlags, out ProductionCost costByCat)) + if (dataAccess.ProductionCost.TryGetValue(ed.Definition, out double value)) { - modifier = costByCat.costModifier; + modifier = value; } - else if (dataAccess.ProductionCostByTechLevel.TryGetValue(ed.Tier.level, out ProductionCost costByLevel)) - { - modifier = costByLevel.costModifier; - } - return modifier; } From e6cd9c57121152ec9a0545fb37b52f89bfa5001f Mon Sep 17 00:00:00 2001 From: mikejeffers Date: Thu, 4 Mar 2021 01:26:50 -0500 Subject: [PATCH 242/457] new category flags, affects how some classes are registered, misc cleanup/refactors --- .../PerpetuumBootstrapper.cs | 26 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1276 +++++++++-------- .../DefinitionNames.cs | 2 +- src/Perpetuum/Players/Player.cs | 7 +- 4 files changed, 673 insertions(+), 638 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index c567ca56e..682f8a4c2 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1247,24 +1247,12 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_visibility_based_probe); 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 - //TODO ORDER MATTERS! Register Paints AFTER lottery will ensure Paint objects are valid subset of lottery category - //TODO entitydefaults must contain name "def_paint" and have cf_lottery_items - //TODO make separate category - ByNamePatternAndFlag("def_paint", CategoryFlags.cf_lottery_items); - - //TODO new CalibrationTemplateItem -- activates like paint! same category! - ByNamePatternAndFlag("_CT_capsule", CategoryFlags.cf_lottery_items); - - // TODO new ep boost item -- activates like paint - ByNamePatternAndFlag("def_boost_ep", CategoryFlags.cf_lottery_items); - - // TODO new Blinder module - ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODUL, CategoryFlags.cf_target_painter); - - //New Relic Definition for Units - ByNamePatternAndFlag(DefinitionNames.RELIC, CategoryFlags.undefined); - ByNamePatternAndFlag(DefinitionNames.RELIC_SAP, CategoryFlags.undefined); + // OPP new Blinder module + ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); ByCategoryFlags(CategoryFlags.cf_gift_packages); @@ -1325,7 +1313,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByName(DefinitionNames.RANDOM_RIFT_PORTAL); ByName(DefinitionNames.BASE_ITEM_SHOP); ByName(DefinitionNames.ANNIVERSARY_PACKAGE); - ByName(DefinitionNames.STRONGHOLD_EXIT_RIFT); + ByName(DefinitionNames.STRONGHOLD_EXIT_RIFT); //OPP stronghold exit rift + ByName(DefinitionNames.RELIC); //OPP Relic + ByName(DefinitionNames.RELIC_SAP); //OPP outpost Relic var c = b.Build(); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index e2e46bdf7..cb9b634b7 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -1,623 +1,669 @@ // 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_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_ct_capsules = 0x000000000003049B, + cf_ct_capsules_ammo = 0x000000000103049B, + cf_ct_capsules_ammo_laser = 0x000000010103049B, + cf_ct_capsules_ammo_missile = 0x000000040103049B, + cf_ct_capsules_ammo_projectile = 0x000000030103049B, + cf_ct_capsules_ammo_railgun = 0x000000020103049B, + cf_ct_capsules_equipment = 0x000000000203049B, + cf_ct_capsules_equipment_armor = 0x000000010203049B, + cf_ct_capsules_equipment_electronics = 0x000000030203049B, + cf_ct_capsules_equipment_engineering = 0x000000040203049B, + cf_ct_capsules_equipment_ewar = 0x000000020203049B, + cf_ct_capsules_equipment_industrial = 0x000000050203049B, + cf_ct_capsules_equipment_nexus = 0x000000080203049B, + cf_ct_capsules_equipment_shield = 0x000000060203049B, + cf_ct_capsules_equipment_weapons = 0x000000070203049B, + cf_ct_capsules_misc = 0x000000000403049B, + cf_ct_capsules_robot = 0x000000000303049B, + cf_ct_capsules_robot_industrial = 0x000000040303049B, + cf_ct_capsules_robot_industrial_assault = 0x000002040303049B, + cf_ct_capsules_robot_industrial_heavymech = 0x000004040303049B, + cf_ct_capsules_robot_industrial_light = 0x000001040303049B, + cf_ct_capsules_robot_industrial_mech = 0x000003040303049B, + cf_ct_capsules_robot_nuimqol = 0x000000030303049B, + cf_ct_capsules_robot_nuimqol_assault = 0x000002030303049B, + cf_ct_capsules_robot_nuimqol_heavymech = 0x000004030303049B, + cf_ct_capsules_robot_nuimqol_light = 0x000001030303049B, + cf_ct_capsules_robot_nuimqol_mech = 0x000003030303049B, + cf_ct_capsules_robot_pelistal = 0x000000010303049B, + cf_ct_capsules_robot_pelistal_assault = 0x000002010303049B, + cf_ct_capsules_robot_pelistal_heavymech = 0x000004010303049B, + cf_ct_capsules_robot_pelistal_light = 0x000001010303049B, + cf_ct_capsules_robot_pelistal_mech = 0x000003010303049B, + cf_ct_capsules_robot_syndicate = 0x000000050303049B, + cf_ct_capsules_robot_syndicate_assault = 0x000002050303049B, + cf_ct_capsules_robot_syndicate_heavymech = 0x000004050303049B, + cf_ct_capsules_robot_syndicate_light = 0x000001050303049B, + cf_ct_capsules_robot_syndicate_mech = 0x000003050303049B, + cf_ct_capsules_robot_thelodica = 0x000000020303049B, + cf_ct_capsules_robot_thelodica_assault = 0x000002020303049B, + cf_ct_capsules_robot_thelodica_heavymech = 0x000004020303049B, + cf_ct_capsules_robot_thelodica_light = 0x000001020303049B, + cf_ct_capsules_robot_thelodica_mech = 0x000003020303049B, + cf_datashards = 0x0000000000000694, + 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_ep_boosters = 0x000000000001049B, + 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_paints = 0x000000000002049B, + 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 } } diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 6d9656651..3fcbcc9a8 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4513,7 +4513,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_MODUL = "def_standard_blinder"; + 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"; diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index c35813ccd..11f13aabf 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -542,15 +542,14 @@ private void HandlePlayerDead(IZone zone, Unit killer) } //Check if paint was applied + paint drop chance - if (this.ED.Config.Tint != this.Tint && LootHelper.Roll(0.5)) + if (ED.Config.Tint != Tint && LootHelper.Roll(0.5)) { //Paint Query //TODO: performance => cache paints in static collection //TODO: performance => cache if painted, and paint entitydef on undock EntityDefault paint = EntityDefault.Reader.GetAll() - .Where(i => i.CategoryFlags == CategoryFlags.cf_lottery_items) - .Where(i => i.Config.Tint == this.Tint) - .Where(i => i.Name.Contains("paint")).First(); + .Where(i => i.CategoryFlags == CategoryFlags.cf_paints) + .Where(i => i.Config.Tint == Tint).First(); if (paint != null) { lootItems.Add(LootItemBuilder.Create(paint.Definition).SetQuantity(1).SetDamaged(false).Build()); From 082e9b5ba5e08837816e11f83286b767aadac349 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Tue, 9 Mar 2021 21:59:07 +0100 Subject: [PATCH 243/457] add dictionary for weather effects --- .../EnvironmentalEffectHandler.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 7594a25a2..0fefd3e68 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -4,6 +4,7 @@ using Perpetuum.Services.Weather; using Perpetuum.Zones; using Perpetuum.Zones.Effects.ZoneEffects; +using System; using System.Collections.Generic; namespace Perpetuum.Services.EventServices.EventProcessors @@ -28,10 +29,28 @@ public class EnvironmentalEffectHandler : EventProcessor EffectType.effect_weather_bad }; private readonly IDictionary _effects = new Dictionary(); + private readonly IDictionary, EffectType> _weatherDict = new Dictionary, EffectType>(); public EnvironmentalEffectHandler(IZone zone) { _zone = zone; _effects = InitEffectCollection(_effectTypes); + _weatherDict = InitWeatherCollection(); + } + + private IDictionary, EffectType> InitWeatherCollection() + { + var dict = new Dictionary, EffectType> + { + { Tuple.Create("day", "neutral"), EffectType.effect_day }, + { Tuple.Create("day", "good"), EffectType.effect_day_clear }, + { Tuple.Create("day", "bad"), EffectType.effect_day_overcast }, + { Tuple.Create("night", "neutral"), EffectType.effect_night }, + { Tuple.Create("night", "good"), EffectType.effect_night_clear }, + { Tuple.Create("night", "bad"), EffectType.effect_night_overcast }, + { Tuple.Create("neutral", "good"), EffectType.effect_weather_good }, + { Tuple.Create("neutral", "bad"), EffectType.effect_weather_bad } + }; + return dict; } private IDictionary InitEffectCollection(EffectType[] effectTypes) From e5b9846e9eed14e3be1a6b4e835066a201e47151 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Tue, 9 Mar 2021 21:59:40 +0100 Subject: [PATCH 244/457] refactor to use new weather dictionary --- .../EnvironmentalEffectHandler.cs | 57 +++++++------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 0fefd3e68..e665d5d4b 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -78,47 +78,34 @@ private void OnStateChange() return; ZoneEffect nextEffect = null; - if (_gameTime.IsDay) + string timeOfDay = "neutral"; + string weatherType = "neutral"; + + if(_gameTime.IsDay) + { + timeOfDay = "day"; + } + else if(_gameTime.IsNight) + { + timeOfDay = "night"; + } + + if(_weatherState.IsGoodWeather) { - if (_weatherState.IsGoodWeather) - { - nextEffect = GetEffect(EffectType.effect_day_clear); - } - else if (_weatherState.IsBadWeather) - { - nextEffect = GetEffect(EffectType.effect_day_overcast); - } - else - { - nextEffect = GetEffect(EffectType.effect_day); - } + weatherType = "good"; } - else if (_gameTime.IsNight) + else if(_weatherState.IsBadWeather) { - if (_weatherState.IsGoodWeather) - { - nextEffect = GetEffect(EffectType.effect_night_clear); - } - else if (_weatherState.IsBadWeather) - { - nextEffect = GetEffect(EffectType.effect_night_overcast); - } - else - { - nextEffect = GetEffect(EffectType.effect_night); - } + weatherType = "bad"; } - else + + //In cases where timeOfDay is supposed to stay "neutral", weatherType cannot also be "neutral" if we are to follow the logic this change is replacing. + if (String.Equals(timeOfDay, weatherType)) { - if (_weatherState.IsGoodWeather) - { - nextEffect = GetEffect(EffectType.effect_weather_good); - } - else if (_weatherState.IsBadWeather) - { - nextEffect = GetEffect(EffectType.effect_weather_bad); - } + return; } + var weatherResult = Tuple.Create(timeOfDay, weatherType); + nextEffect = GetEffect(_weatherDict[weatherResult]); var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || (_currentEffect != null && _currentEffect.Equals(nextEffect)); From 5cafcccf447f5bba3269bde896d22c696fb269da Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Wed, 10 Mar 2021 11:44:25 +0100 Subject: [PATCH 245/457] Add enums for game & weather state --- .../Services/GameTime/GameTimeInfo.cs | 23 +++++++++++++++++++ src/Perpetuum/Services/Weather/WeatherInfo.cs | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/Perpetuum/Services/GameTime/GameTimeInfo.cs b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs index 45d5f7300..d65e732e3 100644 --- a/src/Perpetuum/Services/GameTime/GameTimeInfo.cs +++ b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs @@ -33,6 +33,29 @@ 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; + } + } + public bool IsDay { get diff --git a/src/Perpetuum/Services/Weather/WeatherInfo.cs b/src/Perpetuum/Services/Weather/WeatherInfo.cs index 40b336e20..313c12d16 100644 --- a/src/Perpetuum/Services/Weather/WeatherInfo.cs +++ b/src/Perpetuum/Services/Weather/WeatherInfo.cs @@ -47,6 +47,29 @@ public override string ToString() 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; + } + } + public bool IsBadWeather { get From 7d593c4b4daa8448de3d580515e9b8c985ea8405 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Wed, 10 Mar 2021 11:58:29 +0100 Subject: [PATCH 246/457] EffectHandler using new day-weather enums --- .../EnvironmentalEffectHandler.cs | 48 ++++++------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index e665d5d4b..06d87be59 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -29,7 +29,7 @@ public class EnvironmentalEffectHandler : EventProcessor EffectType.effect_weather_bad }; private readonly IDictionary _effects = new Dictionary(); - private readonly IDictionary, EffectType> _weatherDict = new Dictionary, EffectType>(); + private readonly IDictionary, EffectType> _weatherDict = new Dictionary, EffectType>(); public EnvironmentalEffectHandler(IZone zone) { _zone = zone; @@ -37,18 +37,18 @@ public EnvironmentalEffectHandler(IZone zone) _weatherDict = InitWeatherCollection(); } - private IDictionary, EffectType> InitWeatherCollection() + private IDictionary, EffectType> InitWeatherCollection() { - var dict = new Dictionary, EffectType> + var dict = new Dictionary, EffectType> { - { Tuple.Create("day", "neutral"), EffectType.effect_day }, - { Tuple.Create("day", "good"), EffectType.effect_day_clear }, - { Tuple.Create("day", "bad"), EffectType.effect_day_overcast }, - { Tuple.Create("night", "neutral"), EffectType.effect_night }, - { Tuple.Create("night", "good"), EffectType.effect_night_clear }, - { Tuple.Create("night", "bad"), EffectType.effect_night_overcast }, - { Tuple.Create("neutral", "good"), EffectType.effect_weather_good }, - { Tuple.Create("neutral", "bad"), EffectType.effect_weather_bad } + { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.NEUTRAL_WEATHER), EffectType.effect_day }, + { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_day_clear }, + { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_day_overcast }, + { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.NEUTRAL_WEATHER), EffectType.effect_night }, + { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_night_clear }, + { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_night_overcast }, + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_weather_good }, + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_weather_bad } }; return dict; } @@ -78,33 +78,13 @@ private void OnStateChange() return; ZoneEffect nextEffect = null; - string timeOfDay = "neutral"; - string weatherType = "neutral"; - if(_gameTime.IsDay) - { - timeOfDay = "day"; - } - else if(_gameTime.IsNight) - { - timeOfDay = "night"; - } - - if(_weatherState.IsGoodWeather) - { - weatherType = "good"; - } - else if(_weatherState.IsBadWeather) - { - weatherType = "bad"; - } - - //In cases where timeOfDay is supposed to stay "neutral", weatherType cannot also be "neutral" if we are to follow the logic this change is replacing. - if (String.Equals(timeOfDay, weatherType)) + //In cases where DayState is supposed to stay "neutral", WeatherState cannot also be "neutral" if we are to follow the logic this change is replacing. + if (_gameTime.GetDayState() == GameTimeInfo.DayState.NEUTRAL && _weatherState.getWeatherState() == WeatherInfo.WeatherState.NEUTRAL_WEATHER) { return; } - var weatherResult = Tuple.Create(timeOfDay, weatherType); + var weatherResult = Tuple.Create(_gameTime.GetDayState(), _weatherState.getWeatherState()); nextEffect = GetEffect(_weatherDict[weatherResult]); var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || From d27cf14a8a63c69851d031d96ece0fce0f0bc6af Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Wed, 10 Mar 2021 11:59:23 +0100 Subject: [PATCH 247/457] Cleanup unused code --- src/Perpetuum/Services/GameTime/GameTimeInfo.cs | 16 ---------------- src/Perpetuum/Services/Weather/WeatherInfo.cs | 16 ---------------- 2 files changed, 32 deletions(-) diff --git a/src/Perpetuum/Services/GameTime/GameTimeInfo.cs b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs index d65e732e3..a1d5f6460 100644 --- a/src/Perpetuum/Services/GameTime/GameTimeInfo.cs +++ b/src/Perpetuum/Services/GameTime/GameTimeInfo.cs @@ -55,21 +55,5 @@ public DayState GetDayState() return DayState.NEUTRAL; } } - - public bool IsDay - { - get - { - return GameTimeStamp > DAY_START && GameTimeStamp < DAY_END; - } - } - - public bool IsNight - { - get - { - return GameTimeStamp > NIGHT_START || GameTimeStamp < NIGHT_END; - } - } } } diff --git a/src/Perpetuum/Services/Weather/WeatherInfo.cs b/src/Perpetuum/Services/Weather/WeatherInfo.cs index 313c12d16..859c558cd 100644 --- a/src/Perpetuum/Services/Weather/WeatherInfo.cs +++ b/src/Perpetuum/Services/Weather/WeatherInfo.cs @@ -69,21 +69,5 @@ public WeatherState getWeatherState() return WeatherState.NEUTRAL_WEATHER; } } - - public bool IsBadWeather - { - get - { - return Current > BAD_WEATHER && Next > BAD_WEATHER; - } - } - - public bool IsGoodWeather - { - get - { - return Current < GOOD_WEATHER && Next < GOOD_WEATHER; - } - } } } \ No newline at end of file From e81b72cb7a1364225a7e916031fb1d14f66b30a2 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Wed, 10 Mar 2021 22:38:47 +0100 Subject: [PATCH 248/457] Allow neutral combo to result in Null nextEffect --- .../EventProcessors/EnvironmentalEffectHandler.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 06d87be59..e3ca797e1 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -79,13 +79,11 @@ private void OnStateChange() ZoneEffect nextEffect = null; - //In cases where DayState is supposed to stay "neutral", WeatherState cannot also be "neutral" if we are to follow the logic this change is replacing. - if (_gameTime.GetDayState() == GameTimeInfo.DayState.NEUTRAL && _weatherState.getWeatherState() == WeatherInfo.WeatherState.NEUTRAL_WEATHER) + if (_gameTime.GetDayState() != GameTimeInfo.DayState.NEUTRAL && _weatherState.getWeatherState() != WeatherInfo.WeatherState.NEUTRAL_WEATHER) { - return; - } - var weatherResult = Tuple.Create(_gameTime.GetDayState(), _weatherState.getWeatherState()); - nextEffect = GetEffect(_weatherDict[weatherResult]); + var weatherResult = Tuple.Create(_gameTime.GetDayState(), _weatherState.getWeatherState()); + nextEffect = GetEffect(_weatherDict[weatherResult]); + } var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || (_currentEffect != null && _currentEffect.Equals(nextEffect)); From a35a0f2b15e2a152957c3ba86680f7db8ec996b4 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Thu, 11 Mar 2021 10:03:44 +0100 Subject: [PATCH 249/457] dictionary accepts dynamic instead of explicit EffectType --- .../EnvironmentalEffectHandler.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index e3ca797e1..54d4b239d 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -29,7 +29,7 @@ public class EnvironmentalEffectHandler : EventProcessor EffectType.effect_weather_bad }; private readonly IDictionary _effects = new Dictionary(); - private readonly IDictionary, EffectType> _weatherDict = new Dictionary, EffectType>(); + private readonly IDictionary, dynamic> _weatherDict = new Dictionary, dynamic>(); public EnvironmentalEffectHandler(IZone zone) { _zone = zone; @@ -37,9 +37,9 @@ public EnvironmentalEffectHandler(IZone zone) _weatherDict = InitWeatherCollection(); } - private IDictionary, EffectType> InitWeatherCollection() + private IDictionary, dynamic> InitWeatherCollection() { - var dict = new Dictionary, EffectType> + var dict = new Dictionary, dynamic> { { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.NEUTRAL_WEATHER), EffectType.effect_day }, { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_day_clear }, @@ -48,7 +48,8 @@ public EnvironmentalEffectHandler(IZone zone) { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_night_clear }, { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_night_overcast }, { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_weather_good }, - { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_weather_bad } + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_weather_bad }, + { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.NEUTRAL_WEATHER), null } }; return dict; } @@ -79,11 +80,8 @@ private void OnStateChange() ZoneEffect nextEffect = null; - if (_gameTime.GetDayState() != GameTimeInfo.DayState.NEUTRAL && _weatherState.getWeatherState() != WeatherInfo.WeatherState.NEUTRAL_WEATHER) - { - var weatherResult = Tuple.Create(_gameTime.GetDayState(), _weatherState.getWeatherState()); - nextEffect = GetEffect(_weatherDict[weatherResult]); - } + var weatherResult = Tuple.Create(_gameTime.GetDayState(), _weatherState.getWeatherState()); + nextEffect = GetEffect(_weatherDict[weatherResult]); var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || (_currentEffect != null && _currentEffect.Equals(nextEffect)); From 397627413f2b45d50132459d2deb5f68d4b33ba5 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Thu, 11 Mar 2021 20:19:39 +0100 Subject: [PATCH 250/457] Replace EffectType array with Day/Weather dictionary --- .../EnvironmentalEffectHandler.cs | 55 ++++++------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 54d4b239d..8655f096e 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -18,55 +18,35 @@ public class EnvironmentalEffectHandler : EventProcessor private WeatherInfo _weatherState; private GameTimeInfo _gameTime; private ZoneEffect _currentEffect; - private readonly EffectType[] _effectTypes = new EffectType[] { - EffectType.effect_day, - EffectType.effect_day_clear, - EffectType.effect_day_overcast, - EffectType.effect_night, - EffectType.effect_night_clear, - EffectType.effect_night_overcast, - EffectType.effect_weather_good, - EffectType.effect_weather_bad - }; - private readonly IDictionary _effects = new Dictionary(); - private readonly IDictionary, dynamic> _weatherDict = new Dictionary, dynamic>(); + private readonly IDictionary, ZoneEffect> _effects = new Dictionary< Tuple, ZoneEffect>(); + public EnvironmentalEffectHandler(IZone zone) { _zone = zone; - _effects = InitEffectCollection(_effectTypes); - _weatherDict = InitWeatherCollection(); + _effects = InitEffectCollection(); } - private IDictionary, dynamic> InitWeatherCollection() + private IDictionary, ZoneEffect> InitEffectCollection() { - var dict = new Dictionary, dynamic> + var dict = new Dictionary, ZoneEffect>() { - { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.NEUTRAL_WEATHER), EffectType.effect_day }, - { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_day_clear }, - { Tuple.Create(GameTimeInfo.DayState.DAY, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_day_overcast }, - { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.NEUTRAL_WEATHER), EffectType.effect_night }, - { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_night_clear }, - { Tuple.Create(GameTimeInfo.DayState.NIGHT, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_night_overcast }, - { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.GOOD_WEATHER), EffectType.effect_weather_good }, - { Tuple.Create(GameTimeInfo.DayState.NEUTRAL, WeatherInfo.WeatherState.BAD_WEATHER), EffectType.effect_weather_bad }, + { 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 IDictionary InitEffectCollection(EffectType[] effectTypes) + private ZoneEffect GetEffect(GameTimeInfo.DayState dayState, WeatherInfo.WeatherState weatherState) { - var dict = new Dictionary(); - foreach (var effType in effectTypes) - { - dict.Add(effType, new ZoneEffect(_zone.Id, effType, true)); - } - return dict; - } - - private ZoneEffect GetEffect(EffectType type) - { - if (_effects.TryGetValue(type, out ZoneEffect effect)) + var lookupEffect = Tuple.Create(dayState, weatherState); + if (_effects.TryGetValue(lookupEffect, out ZoneEffect effect)) { return effect; } @@ -80,8 +60,7 @@ private void OnStateChange() ZoneEffect nextEffect = null; - var weatherResult = Tuple.Create(_gameTime.GetDayState(), _weatherState.getWeatherState()); - nextEffect = GetEffect(_weatherDict[weatherResult]); + nextEffect = GetEffect(_gameTime.GetDayState(), _weatherState.getWeatherState()); var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || (_currentEffect != null && _currentEffect.Equals(nextEffect)); From d24abe0aef52f57c94b7067648ee8a56cf177347 Mon Sep 17 00:00:00 2001 From: MuuhMuuh Date: Fri, 12 Mar 2021 17:07:28 +0100 Subject: [PATCH 251/457] Remove null init and code cleanup --- .../EventProcessors/EnvironmentalEffectHandler.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 8655f096e..4b502437c 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -18,8 +18,8 @@ public class EnvironmentalEffectHandler : EventProcessor private WeatherInfo _weatherState; private GameTimeInfo _gameTime; private ZoneEffect _currentEffect; - private readonly IDictionary, ZoneEffect> _effects = new Dictionary< Tuple, ZoneEffect>(); - + private readonly IDictionary, ZoneEffect> _effects = new Dictionary, ZoneEffect>(); + public EnvironmentalEffectHandler(IZone zone) { _zone = zone; @@ -58,9 +58,7 @@ private void OnStateChange() if (_gameTime == null || _weatherState == null) return; - ZoneEffect nextEffect = null; - - nextEffect = GetEffect(_gameTime.GetDayState(), _weatherState.getWeatherState()); + var nextEffect = GetEffect(_gameTime.GetDayState(), _weatherState.getWeatherState()); var isSameEffect = ReferenceEquals(_currentEffect, nextEffect) || (_currentEffect != null && _currentEffect.Equals(nextEffect)); From f30f0d1413fffdc591074b8485a85d97bfaed97e Mon Sep 17 00:00:00 2001 From: Veritania Date: Fri, 12 Mar 2021 23:12:43 +0100 Subject: [PATCH 252/457] 213: lootInfos init as empty list --- src/Perpetuum/Services/Looting/LootGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 90ca9030b..c6b93ef57 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -9,7 +9,7 @@ namespace Perpetuum.Services.Looting public class LootGenerator : ILootGenerator { - private readonly IEnumerable _lootInfos; + private readonly IEnumerable _lootInfos = new List(); public LootGenerator(IEnumerable lootInfos) { @@ -38,7 +38,7 @@ public IEnumerable Generate() public class SplittableLootGenerator : ISplittableLootGenerator { - private readonly IReadOnlyCollection _lootInfos; + private readonly IReadOnlyCollection _lootInfos = new List(); private readonly Random _random; public SplittableLootGenerator(ILootGenerator lootGenerator) From db10c4f64319d61ec27874a9286165e6c13e2e40 Mon Sep 17 00:00:00 2001 From: Veritania Date: Sun, 14 Mar 2021 14:01:11 +0100 Subject: [PATCH 253/457] LootGenerator empty list if given null param --- src/Perpetuum/Services/Looting/LootGenerator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index c6b93ef57..523853563 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -13,6 +13,10 @@ public class LootGenerator : ILootGenerator public LootGenerator(IEnumerable lootInfos) { + if (lootInfos == null) + { + lootInfos = new List(); + } _lootInfos = lootInfos; } From ba8c98559395bf294175d7e49a4b9e5afb87b3b5 Mon Sep 17 00:00:00 2001 From: Veritania Date: Sun, 14 Mar 2021 14:01:31 +0100 Subject: [PATCH 254/457] Clean unused import --- src/Perpetuum/Services/Looting/LootGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 523853563..23a46591f 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -1,5 +1,4 @@ using Perpetuum.Items; -using Perpetuum.Log; using System; using System.Collections.Generic; using System.Linq; From 283ee1131917e4e169106a47fde9807f91cc71d9 Mon Sep 17 00:00:00 2001 From: Veritania Date: Sun, 14 Mar 2021 14:23:24 +0100 Subject: [PATCH 255/457] set _lootInfos to empty list and return early --- src/Perpetuum/Services/Looting/LootGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Looting/LootGenerator.cs b/src/Perpetuum/Services/Looting/LootGenerator.cs index 23a46591f..65bb42489 100644 --- a/src/Perpetuum/Services/Looting/LootGenerator.cs +++ b/src/Perpetuum/Services/Looting/LootGenerator.cs @@ -14,7 +14,8 @@ public LootGenerator(IEnumerable lootInfos) { if (lootInfos == null) { - lootInfos = new List(); + _lootInfos = new List(); + return; } _lootInfos = lootInfos; } From deabb4e7146c85b8584179d694e5f1bddd0950a7 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 17 Mar 2021 19:59:30 -0400 Subject: [PATCH 256/457] Nested container range check (#282) for field terminals --- .../Containers/ZoneContainerRequestHandler.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs index 353442e50..39a404787 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,38 @@ 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; } + private static FieldTerminal GetFieldTerminal(Entity container) + { + if (container == null) + return null; + + var fieldTerminal = container.ParentEntity as FieldTerminal; + while (fieldTerminal == null) + { + container = container.ParentEntity; + if(container == null) + break; + + fieldTerminal = container.ParentEntity as FieldTerminal; + } + 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); From 66e678d0bedade47d0278c40aeba1ef492e4c1a4 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 21 Mar 2021 13:24:48 -0400 Subject: [PATCH 257/457] Email on register fix (#283) --- src/Perpetuum/Services/Sessions/SessionManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Sessions/SessionManager.cs b/src/Perpetuum/Services/Sessions/SessionManager.cs index 8872b2935..bcb7fec9b 100644 --- a/src/Perpetuum/Services/Sessions/SessionManager.cs +++ b/src/Perpetuum/Services/Sessions/SessionManager.cs @@ -81,7 +81,8 @@ 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)) From f3d7badac7651d6e22146326192dce976b7e5672 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 24 Mar 2021 20:01:58 -0400 Subject: [PATCH 258/457] Nic for tutorial completion (#286) --- .../Zones/Teleporting/Strategies/TrainingExitStrategy.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index 5909f7cdf..48084aac9 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -17,6 +17,7 @@ namespace Perpetuum.Zones.Teleporting.Strategies public class TrainingExitStrategy : ITeleportStrategy { private const double CHARACTER_START_CREDIT = 500000; //TODO: move to DB + private const double NIC_REWARD_PER_LEVEL = 125000; //TODO: move to DB 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); @@ -68,7 +69,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 From 6ae777c251a8f2f308af2e70bb1f83fb4656e5a9 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 24 Mar 2021 20:29:52 -0400 Subject: [PATCH 259/457] Npc event anomaly spawn (#276) --- .../PerpetuumBootstrapper.cs | 8 +- .../DefinitionNames.cs | 1 + src/Perpetuum/ErrorCodes.cs | 3 +- src/Perpetuum/Perpetuum.csproj | 3 + .../EventMessages/SpawnPortalMessage.cs | 17 ++ .../EventProcessors/PortalSpawner.cs | 69 ++++++++ .../Services/RiftSystem/CustomRiftConfig.cs | 156 ++++++++++++++++++ src/Perpetuum/Services/RiftSystem/Rift.cs | 63 +++++++ .../Flocks/FlockConfigurationRepository.cs | 6 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 54 +++++- 10 files changed, 367 insertions(+), 13 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs create mode 100644 src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 682f8a4c2..66891c258 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1014,6 +1014,7 @@ 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(); @@ -1313,7 +1314,8 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByName(DefinitionNames.RANDOM_RIFT_PORTAL); ByName(DefinitionNames.BASE_ITEM_SHOP); ByName(DefinitionNames.ANNIVERSARY_PACKAGE); - ByName(DefinitionNames.STRONGHOLD_EXIT_RIFT); //OPP stronghold exit rift + 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 @@ -1440,6 +1442,8 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { + _builder.RegisterType().As(); + _builder.RegisterType().SingleInstance(); _builder.RegisterType().SingleInstance().As(); _builder.RegisterType().As(); @@ -1698,6 +1702,7 @@ private void InitRelayManager() _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType().As>(); _builder.RegisterType().As>(); _builder.RegisterType().SingleInstance().OnActivated(e => @@ -1705,6 +1710,7 @@ private void InitRelayManager() 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()); var obs = new GameTimeObserver(e.Instance); obs.Subscribe(e.Context.Resolve()); }); diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 3fcbcc9a8..ecb8228b1 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4748,6 +4748,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/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index d28566fae..5f414d665 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -701,6 +701,7 @@ public enum ErrorCodes MissionNoItemsFoundToDeliver, InviteOnlyServer, SteamLoginDisabled, - AccountAlreadyExists + AccountAlreadyExists, + RobotWrongType } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index b21a17c18..b5495b46f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -291,9 +291,11 @@ + + @@ -332,6 +334,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs new file mode 100644 index 000000000..f6b4a6cf0 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs @@ -0,0 +1,17 @@ +using Perpetuum.Services.RiftSystem; + +namespace Perpetuum.Services.EventServices.EventMessages +{ + public class SpawnPortalMessage : EventMessage + { + 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; + } + } +} diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs new file mode 100644 index 000000000..8133d84ed --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs @@ -0,0 +1,69 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.RiftSystem; +using Perpetuum.Zones; + +namespace Perpetuum.Services.EventServices.EventProcessors +{ + public class PortalSpawner : EventProcessor + { + private readonly IEntityServices _entityServices; + private readonly IZoneManager _zoneManager; + + 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; + if (!_zoneManager.GetZone(msg.SourceZone).IsWalkable(msg.SourcePosition)) + return false; + return true; + } + + private Destination TryGetValidDestination(CustomRiftConfig riftConfig) + { + var attempts = 100; + while (attempts > 0) + { + attempts--; + var dest = riftConfig.GetDestination(); + if (dest != null && _zoneManager.ContainsZone(dest.ZoneId)) + { + return dest; + } + } + return null; + } + + public override void HandleMessage(EventMessage value) + { + if (value is SpawnPortalMessage msg) + { + if (!ValidateMessage(msg)) + return; + + var zone = _zoneManager.GetZone(msg.SourceZone); + var targetDestination = TryGetValidDestination(msg.RiftConfig); + if (targetDestination == null) + return; + + var zoneTarget = _zoneManager.GetZone(targetDestination.ZoneId); + var targetPos = targetDestination.GetPosition(zoneTarget); + var rift = (TargettedPortal)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.TARGETTED_RIFT); + rift.AddToZone(zone, msg.SourcePosition, ZoneEnterType.NpcSpawn); + rift.SetTarget(zoneTarget, targetPos); + rift.SetConfig(msg.RiftConfig); + + Logger.Info(string.Format("TargettedRift spawned on zone {0} {1} ({2})", zone.Id, rift.ED.Name, rift.CurrentPosition)); + } + } + + } +} diff --git a/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs new file mode 100644 index 000000000..7891eb7ae --- /dev/null +++ b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs @@ -0,0 +1,156 @@ +using Perpetuum.Collections; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +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;") + .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 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 Destination GetDestination() + { + return _destinations.GetRandom(); + } + + public bool IsExcluded(CategoryFlags category) + { + return category.IsAny(ExcludeClasses); + } + } + + + 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/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index b917fadef..46ed6bf1e 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -48,6 +48,69 @@ protected override void OnUpdate(TimeSpan time) } } + public class TargettedPortal : DespawningPortal + { + public IZone TargetZone { get; private set; } + public Position TargetPosition { get; private set; } + public CustomRiftConfig RiftConfig { get; private set; } + private readonly ITeleportStrategyFactories _teleportStrategyFactories; + private int uses = 0; + + public TargettedPortal(ITeleportStrategyFactories teleportStrategyFactories) + { + _teleportStrategyFactories = teleportStrategyFactories; + } + + public void SetConfig(CustomRiftConfig riftConfig) + { + RiftConfig = riftConfig; + if (RiftConfig.IsDespawning) + { + SetDespawnTime(RiftConfig.Lifespan); + } + } + + public void SetTarget(IZone zone, Position position) + { + TargetZone = zone; + TargetPosition = position; + } + + 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) + { + base.UseItem(player); + + IsUsageExceeded().ThrowIfTrue(ErrorCodes.MaximumAllowedRegistrationExceeded); + + IsExcluded(player).ThrowIfTrue(ErrorCodes.RobotWrongType); + + var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(TargetZone); + teleport.TargetPosition = TargetPosition; + teleport.DoTeleportAsync(player); + + IncrementUsage(); + + if (IsUsageExceeded()) + Kill(); + } + } + /// /// RandomRift (used as the Rift Echo after a TAP) /// diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs index 72a983bed..0f2fee557 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -8,12 +8,14 @@ namespace Perpetuum.Zones.NpcSystem.Flocks { public class FlockConfigurationRepository : IFlockConfigurationRepository { + private readonly NpcBossInfoBuilder _bossBuilder; private readonly FlockConfigurationBuilder.Factory _flockConfigurationBuilderFactory; 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() @@ -40,7 +42,7 @@ public void LoadAllConfig() c.Enabled = r.GetValue("enabled"); c.BehaviorType = (NpcBehaviorType) r.GetValue("behaviorType"); c.SpecialType = (NpcSpecialType) r.GetValue("npcSpecialType"); - c.BossInfo = NpcBossInfo.GetBossInfoByFlockID(c.ID); + c.BossInfo = _bossBuilder.GetBossInfoByFlockID(c.ID); }); var config = builder.Build(); _flockConfigurations[config.ID] = config; diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index c14597e38..bb01b5235 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -2,6 +2,7 @@ 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; @@ -13,12 +14,15 @@ namespace Perpetuum.Zones.NpcSystem { - /// - /// Specifies the behavior of a Boss-type NPC with various settings - /// - public class NpcBossInfo + public class NpcBossInfoBuilder { - public static NpcBossInfo CreateBossInfoFromDB(IDataRecord record) + private readonly ICustomRiftConfigReader _customRiftConfigReader; + public NpcBossInfoBuilder(ICustomRiftConfigReader customRiftConfigReader) + { + _customRiftConfigReader = customRiftConfigReader; + } + + public NpcBossInfo CreateBossInfoFromDB(IDataRecord record) { var id = record.GetValue("id"); var flockid = record.GetValue("flockid"); @@ -29,15 +33,28 @@ public static NpcBossInfo CreateBossInfoFromDB(IDataRecord record) var overrideRelations = record.GetValue("overrideRelations"); var deathMessage = record.GetValue("customDeathMessage"); var aggressMessage = record.GetValue("customAggressMessage"); - var info = new NpcBossInfo(id, flockid, respawnFactor, lootSplit, outpostEID, stabilityPts, overrideRelations, deathMessage, aggressMessage); + var riftConfigId = record.GetValue("riftConfigId"); + var riftConfig = _customRiftConfigReader.GetById(riftConfigId ?? -1); + var info = new NpcBossInfo(id, + flockid, + respawnFactor, + lootSplit, + outpostEID, + stabilityPts, + overrideRelations, + deathMessage, + aggressMessage, + riftConfig + ); return info; } - public static NpcBossInfo GetBossInfoByFlockID(int flockid) + public NpcBossInfo GetBossInfoByFlockID(int flockid) { var bossInfos = Db.Query() - .CommandText(@"SELECT TOP 1 id, flockid, respawnNoiseFactor, lootSplitFlag, outpostEID, stabilityPts, overrideRelations, customDeathMessage, customAggressMessage + .CommandText(@"SELECT TOP 1 id, flockid, respawnNoiseFactor, lootSplitFlag, outpostEID, + stabilityPts, overrideRelations, customDeathMessage, customAggressMessage, riftConfigId FROM dbo.npcbossinfo WHERE flockid=@flockid;") .SetParameter("@flockid", flockid) .Execute() @@ -45,13 +62,21 @@ public static NpcBossInfo GetBossInfoByFlockID(int flockid) return bossInfos.SingleOrDefault(); } + } + + /// + /// Specifies the behavior of a Boss-type NPC with various settings + /// + public class NpcBossInfo + { 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; public int FlockId { get; } @@ -59,11 +84,12 @@ public static NpcBossInfo GetBossInfoByFlockID(int flockid) private bool IsOutpostBoss { get { return _outpostEID != null; } } private int StabilityPoints { get { return _stabilityPts ?? 0; } } private bool OverrideRelations { get; } + private bool HasRiftToSpawn { get { return _riftConfig != null; } } public bool IsLootSplit { get; } public bool IsDead { get; private set; } - public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg) + public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg, CustomRiftConfig riftConfig) { _id = id; FlockId = flockid; @@ -74,6 +100,7 @@ public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSpl OverrideRelations = overrideRelations; _deathMsg = customDeathMsg; _aggroMsg = customAggroMsg; + _riftConfig = riftConfig; _speak = true; IsDead = false; } @@ -125,6 +152,7 @@ public void OnDeath(Npc npc, Unit killer, EventListenerService channel) { CommunicateDeath(killer, channel); HandleBossOutpostDeath(npc, killer, channel); + SpawnPortal(npc, killer, channel); IsDead = true; channel.PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); } @@ -194,6 +222,14 @@ private void HandleBossOutpostDeath(Npc npc, Unit killer, EventListenerService c } } + private void SpawnPortal(Npc npc, Unit killer, EventListenerService channel) + { + if (!HasRiftToSpawn) + return; + + channel.PublishMessage(new SpawnPortalMessage(npc.Zone.Id, npc.CurrentPosition, _riftConfig)); + } + private static void SendMessage(Unit src, EventListenerService eventChannel, string msg) { if (!msg.IsNullOrEmpty()) From bd606e24b869e1b91673c39c8ddbf7d34cf07006 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 24 Mar 2021 22:40:41 -0400 Subject: [PATCH 260/457] mission rewards linear (#287) --- .../MissionTargets/MissionTargetRewardCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index 0b5c94fd4..6ddf59067 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -68,7 +68,7 @@ private void PayAsMineral() } //ECONOMY! TODO put this in DB! - public static readonly double[] PayOutMultipliers = new double[] { 100, 175, 250, 350, 475, 600, 700, 800, 900, 1000 }; + public static readonly double[] PayOutMultipliers = new double[] { 100, 175, 250, 325, 400, 475, 550, 625, 700, 775 }; private void PayReward() { From d062c51ee09ab2f10563ee6224a22ba097c89014 Mon Sep 17 00:00:00 2001 From: Veritania Date: Sat, 27 Mar 2021 19:52:16 +0100 Subject: [PATCH 261/457] Issue 257: Server "Admins only" mode (#290) * Add new AdminCommands RelayOpen and RelayClose This allows for admins to call #relayclose or #relayopen in a #secure chat channel, to "block" the LOGIN attempts from non-admin level accounts. --- .../ChatCommands/AdminCommandHandlers.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 54ebe8431..50e99b708 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -144,6 +144,23 @@ public static void Shutdown(AdminCommandData data) 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) { From 3b491282ee5d90f973b817e2122b5aaafdd743f4 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 28 Mar 2021 12:43:29 -0400 Subject: [PATCH 262/457] Missionreward rebalance and protect array accessor (#292) --- .../MissionTargetRewardCalculator.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs index 6ddf59067..dc7230db7 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/MissionTargetRewardCalculator.cs @@ -68,15 +68,19 @@ private void PayAsMineral() } //ECONOMY! TODO put this in DB! - public static readonly double[] PayOutMultipliers = new double[] { 100, 175, 250, 325, 400, 475, 550, 625, 700, 775 }; + 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); } @@ -90,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); @@ -114,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; From bfbc3148659711324e1b0866a0c640e3bb5980d3 Mon Sep 17 00:00:00 2001 From: ajsv3722 Date: Wed, 31 Mar 2021 09:52:14 +0300 Subject: [PATCH 263/457] NPCs start using energy drains immediately. Small ENERGY_VAMPIRE_CORE_THRESHOLD must be used. Even more charge reduction is possible if the target is also empty. --- src/Perpetuum/Zones/NpcSystem/Turret.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 92282157c..aeedee438 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -230,11 +230,11 @@ public void Visit(EnergyNeutralizerModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } - private const double ENERGY_VAMPIRE_CORE_THRESHOLD = 0.8; + private const double ENERGY_VAMPIRE_CORE_THRESHOLD = 0.05; public void Visit(EnergyVampireModule module) { - if ( module.ParentRobot.CorePercentage >= ENERGY_VAMPIRE_CORE_THRESHOLD ) + if ( module.ParentRobot.CorePercentage < ENERGY_VAMPIRE_CORE_THRESHOLD ) return; var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); From e810f395da3f31289ca289758ccdd663af0dbecc Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:30:25 -0400 Subject: [PATCH 264/457] datashards are Item (#295) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 66891c258..3c1feb7f1 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1251,6 +1251,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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 // OPP new Blinder module ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); From 39553b4a9b66aa96e5d16db25a24249b4ea567d9 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Tue, 6 Apr 2021 00:07:15 -0400 Subject: [PATCH 265/457] Itemshop query ED stability (#299) Prevent client crashes from returning Entitydefaults that are hidden or disabled --- src/Perpetuum/Services/ItemShop/ItemShop.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/ItemShop/ItemShop.cs b/src/Perpetuum/Services/ItemShop/ItemShop.cs index a8d0197ab..096b28476 100644 --- a/src/Perpetuum/Services/ItemShop/ItemShop.cs +++ b/src/Perpetuum/Services/ItemShop/ItemShop.cs @@ -40,7 +40,9 @@ 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(); if (record == null) @@ -54,7 +56,9 @@ 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(); From 0fce8c713e3882135f46c4a49eeee6d114588b93 Mon Sep 17 00:00:00 2001 From: Veritania Date: Thu, 8 Apr 2021 16:53:00 +0200 Subject: [PATCH 266/457] Issue 229: arg check on admincommands (#291) Stronger argument validation --- .../ChatCommands/AdminCommandHandlers.cs | 286 +++++++++++++----- 1 file changed, 208 insertions(+), 78 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 50e99b708..84a669c5d 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -106,6 +106,15 @@ public static void CheckZoneId(AdminCommandData data, int zoneId) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } } + + public static void CheckRequiredArgLength(AdminCommandData data, int expectedLength) + { + if(data.Command.Args.Length != expectedLength) + { + SendMessageToAll(data, $"Error - Command expects " + expectedLength.ToString() + " args, but was given " +data.Command.Args.Length.ToString()+ " args"); + throw PerpetuumException.Create(ErrorCodes.TooManyOrTooFewArguments); + } + } public static void Unknown(AdminCommandData data) { SendMessageToAll(data, $"Unknown command: {data.Command.Name}"); @@ -170,18 +179,29 @@ public static void ShutdownCancel(AdminCommandData data) [ChatCommand("JumpTo")] public static void JumpTo(AdminCommandData data) { - bool err = false; //TODO this only throws on bad last-arg! - err = !int.TryParse(data.Command.Args[0], out int zone); - err = !int.TryParse(data.Command.Args[1], out int x); - err = !int.TryParse(data.Command.Args[2], out int y); - if (err) + int zoneId; + int x; + int y; + CheckRequiredArgLength(data, 3); + + try { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + 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" , zone }, + { "zoneID" , zoneId }, { "x" , x }, { "y" , y } }; @@ -192,31 +212,43 @@ public static void JumpTo(AdminCommandData data) [ChatCommand("MovePlayer")] public static void MovePlayer(AdminCommandData data) { - bool err = false; //TODO this parse checking is broken - err = !int.TryParse(data.Command.Args[0], out int characterID); - err = !int.TryParse(data.Command.Args[1], out int zoneID); - err = !int.TryParse(data.Command.Args[2], out int x); - err = !int.TryParse(data.Command.Args[3], out int y); - if (err) + int characterId; + int zoneId; + int x; + int y; + CheckRequiredArgLength(data, 4); + + try { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + 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. - var charactersession = data.SessionManager.GetByCharacter(characterID); + var 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)); + SendMessageToAll(data, string.Format("ERR: Character with ID {0} does not have a zone. Are they docked?", characterId)); return; } // get destination zone. - var zone = data.Request.Session.ZoneMgr.GetZone(zoneID); + var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); if (charactersession.Character.ZoneId == null) { - SendMessageToAll(data, string.Format("ERR: Invalid Zone ID {0}", zoneID)); + SendMessageToAll(data, string.Format("ERR: Invalid Zone ID {0}", zoneId)); return; } @@ -234,11 +266,12 @@ public static void MovePlayer(AdminCommandData data) tp.DoTeleportAsync(player); tp = null; - SendMessageToAll(data, string.Format("Moved Character {0}-{1} to Zone {2} @ {3},{4}", characterID, charactersession.Character.Nick, zone.Id, x, y)); + 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); @@ -307,6 +340,7 @@ public static void GetLockedTileProperties(AdminCommandData data) [ChatCommand("SetVisibility")] public static void SetVisibility(AdminCommandData data) { + CheckRequiredArgLength(data, 1); bool.TryParse(data.Command.Args[0], out bool visiblestate); var character = data.Request.Session.Character; @@ -344,6 +378,7 @@ public static void ZoneDrawStatMap(AdminCommandData data) 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")); @@ -398,15 +433,28 @@ public static void ListRifts(AdminCommandData data) [ChatCommand("FlagPlayerNameOffensive")] public static void FlagPlayerNameOffensive(AdminCommandData data) { - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int characterID); - err = !bool.TryParse(data.Command.Args[1], out bool isoffensive); + 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? - var charactersession = data.SessionManager.GetByCharacter(characterID); - charactersession.Character.IsOffensiveNick = isoffensive; + var 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)); + 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) @@ -415,6 +463,8 @@ public static void RenameCorp(AdminCommandData data) string desiredCorpName = data.Command.Args[1]; string desiredCorpNick = data.Command.Args[2]; + CheckRequiredArgLength(data, 3); + Corporation.IsNameOrNickTaken(desiredCorpName, desiredCorpNick).ThrowIfTrue(ErrorCodes.NameTaken); var corp = Corporation.GetByName(currentCorpName); corp.SetName(desiredCorpName, desiredCorpNick); @@ -424,16 +474,22 @@ public static void RenameCorp(AdminCommandData data) [ChatCommand("UnlockAllEP")] public static void UnlockAllEP(AdminCommandData data) { - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int accountID); - if (err) + int accountId; + + try { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + 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 } + { k.accountID, accountId } }; string cmd = string.Format("{0}:relay:{1}", Commands.ExtensionFreeAllLockedEpCommand.Text, GenxyConverter.Serialize(dictionary)); @@ -443,12 +499,20 @@ public static void UnlockAllEP(AdminCommandData data) [ChatCommand("EPBonusSet")] public static void EPBonusSet(AdminCommandData data) { - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int bonusBoost); - err = !int.TryParse(data.Command.Args[1], out int hours); - if (err) + int bonusBoost; + int hours; + + CheckRequiredArgLength(data, 2); + + try { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + 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() @@ -516,14 +580,24 @@ public static void SetWeather(AdminCommandData data) SendMessageToAll(data, $"bad or missing args"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } + var zoneId = data.Sender.ZoneId ?? -1; - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int weatherInt); - err = !int.TryParse(data.Command.Args[1], out int seconds); - if (data.Command.Args.Length > 2) + int weatherInt; + int seconds; + try { - err = !int.TryParse(data.Command.Args[2], out zoneId); + 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); var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); var current = zone.Weather.GetCurrentWeather(); @@ -640,24 +714,43 @@ public static void ZoneAddDecor(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int definition); - err = !int.TryParse(data.Command.Args[1], out int x); - err = !int.TryParse(data.Command.Args[2], out int y); - err = !int.TryParse(data.Command.Args[3], out int z); - err = !double.TryParse(data.Command.Args[4], out double qx); - err = !double.TryParse(data.Command.Args[5], out double qy); - err = !double.TryParse(data.Command.Args[6], out double qz); - err = !double.TryParse(data.Command.Args[7], out double qw); - err = !double.TryParse(data.Command.Args[8], out double scale); - err = !int.TryParse(data.Command.Args[9], out int cat); - - if (err) + 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 { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + 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; } - Dictionary dictionary = new Dictionary() + string cmd() + { + Dictionary dictionary = new Dictionary() { { "definition", definition }, { "x", x*256 }, @@ -671,8 +764,10 @@ public static void ZoneAddDecor(AdminCommandData data) { "category", cat } }; - string cmd = string.Format("zoneDecorAdd:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); - HandleLocalRequest(data, cmd); + return string.Format("zoneDecorAdd:zone_{0}:{1}", zoneId, GenxyConverter.Serialize(dictionary)); + } + + HandleLocalRequest(data, cmd()); } [ChatCommand("ZoneAddDecorToLockedTile")] public static void ZoneAddDecorToLockedTile(AdminCommandData data) @@ -694,12 +789,19 @@ public static void ZoneAddDecorToLockedTile(AdminCommandData data) double y = terrainLock.Location.Y; double z = terrainLock.Location.Z; - bool err = !double.TryParse(data.Command.Args[1], out double scale); - err = !int.TryParse(data.Command.Args[0], out int definition); + int definition; + double scale; - if (err) + try { - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + 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() @@ -868,9 +970,20 @@ public static void ZoneLockDecor(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int id); - err = !int.TryParse(data.Command.Args[1], out int locked); + 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() { @@ -991,7 +1104,9 @@ public static void ZoneCreateGarden(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - bool err = false; + int x; + int y; + var dictionary = new Dictionary(); if (data.Command.Args.Length != 2) { @@ -999,13 +1114,19 @@ public static void ZoneCreateGarden(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - err = !int.TryParse(data.Command.Args[0], out int x); - err = !int.TryParse(data.Command.Args[1], out int y); - if (err) + try + { + x = int.Parse(data.Command.Args[0]); + y = int.Parse(data.Command.Args[1]); + } + catch (Exception ex) { SendMessageToAll(data, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + if (ex is ArgumentNullException) + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + throw; } + dictionary.Add(k.x, x); dictionary.Add(k.y, y); var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateGarden.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); @@ -1080,7 +1201,6 @@ public static void SpawnRelic(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - bool err = false; var character = data.Request.Session.Character; var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); var player = zone.GetPlayer(character.ActiveRobotEid); @@ -1089,6 +1209,24 @@ public static void SpawnRelic(AdminCommandData data) 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 (terrainLock == null) { if (data.Command.Args.Length != 3) @@ -1096,14 +1234,6 @@ public static void SpawnRelic(AdminCommandData data) SendMessageToAll(data, "Bad args"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - err = !int.TryParse(data.Command.Args[0], out int xCommand); - err = !int.TryParse(data.Command.Args[1], out int yCommand); - err = !int.TryParse(data.Command.Args[2], out int zoneCommand); - if (err) - { - SendMessageToAll(data, "Bad args"); - throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); - } x = xCommand; y = yCommand; zoneid = zoneCommand; From 5e314a66079f02be8839d8f089bb18c8d5f064fe Mon Sep 17 00:00:00 2001 From: Veritania Date: Thu, 8 Apr 2021 16:54:55 +0200 Subject: [PATCH 267/457] Issue 257: Support for OnlyAllowAdmins as a server startup config (#297) * Support for StartServerInAdminOnlyMode as a server startup config value --- src/Perpetuum.AdminTool/LocalServerRunner.cs | 36 ++++++++++++++----- src/Perpetuum.RequestHandlers/RelayClose.cs | 2 ++ src/Perpetuum.RequestHandlers/RelayOpen.cs | 2 ++ src/Perpetuum/GlobalConfiguration.cs | 2 ++ .../Services/Relay/IRelayStateService.cs | 1 + .../Services/Relay/RelayInfoBuilder.cs | 11 ++++++ .../Services/Relay/RelayStateService.cs | 6 ++++ 7 files changed, 51 insertions(+), 9 deletions(-) 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.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/GlobalConfiguration.cs b/src/Perpetuum/GlobalConfiguration.cs index 85d8a6665..49be1c097 100644 --- a/src/Perpetuum/GlobalConfiguration.cs +++ b/src/Perpetuum/GlobalConfiguration.cs @@ -21,5 +21,7 @@ public class GlobalConfiguration public bool EnableDev { get; set; } public CorporationConfiguration Corporation { get; set; } + + public bool StartServerInAdminOnlyMode { get; set; } } } \ 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/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 From 1124ae377afe2707f677e18c887d2b5dc774c14a Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 15 Apr 2021 20:23:05 -0400 Subject: [PATCH 268/457] Stronghold Exit Rift overhaul: use TargettedPortal! (#301) --- .../PerpetuumBootstrapper.cs | 4 +- .../Collections/WeightedCollection.cs | 9 +++- src/Perpetuum/Perpetuum.csproj | 1 - .../EventProcessors/PortalSpawner.cs | 33 ++------------ .../Services/RiftSystem/CustomRiftConfig.cs | 43 ++++++++++++++++--- src/Perpetuum/Services/RiftSystem/Rift.cs | 38 +++++++++++++--- .../StrongholdExitLocations.cs | 12 ++++-- .../StrongholdRifts/StrongholdExitRift.cs | 40 ----------------- .../StrongholdRifts/StrongholdRiftManager.cs | 15 ++++--- 9 files changed, 99 insertions(+), 96 deletions(-) delete mode 100644 src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 3c1feb7f1..f88a9e359 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1014,7 +1014,7 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit(); RegisterUnit(); - RegisterUnit(); // OPP: Special rift spawned eventfully to transport player to location + RegisterUnit(); // OPP: Special rift spawned eventfully to transport player to location RegisterUnit(); // OPP: Special rift for exiting strongholds RegisterEntity(); @@ -1316,7 +1316,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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.TARGETTED_RIFT); //OPP targetted rift ByName(DefinitionNames.RELIC); //OPP Relic ByName(DefinitionNames.RELIC_SAP); //OPP outpost Relic diff --git a/src/Perpetuum/Collections/WeightedCollection.cs b/src/Perpetuum/Collections/WeightedCollection.cs index 32d7933be..485993190 100644 --- a/src/Perpetuum/Collections/WeightedCollection.cs +++ b/src/Perpetuum/Collections/WeightedCollection.cs @@ -28,12 +28,17 @@ public T GetRandom() if (_sumWeights == 0) return default; - var weightTarget = FastRandom.NextInt(_sumWeights); + if (_list.Count == 1) + return _list.First().Item; + + var weightTarget = FastRandom.NextInt(_sumWeights - 1); var current = 0; var iterator = _list.GetEnumerator(); - while (current <= weightTarget && iterator.MoveNext()) + while (iterator.MoveNext()) { current += iterator.Current.Weight; + if (current > weightTarget) + break; } if (iterator.Current != null) { diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index b5495b46f..c9ef33148 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -338,7 +338,6 @@ - diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs index 8133d84ed..4f4fd03fd 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs @@ -1,6 +1,5 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Log; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Services.RiftSystem; using Perpetuum.Zones; @@ -27,21 +26,6 @@ private bool ValidateMessage(SpawnPortalMessage msg) return true; } - private Destination TryGetValidDestination(CustomRiftConfig riftConfig) - { - var attempts = 100; - while (attempts > 0) - { - attempts--; - var dest = riftConfig.GetDestination(); - if (dest != null && _zoneManager.ContainsZone(dest.ZoneId)) - { - return dest; - } - } - return null; - } - public override void HandleMessage(EventMessage value) { if (value is SpawnPortalMessage msg) @@ -49,21 +33,12 @@ public override void HandleMessage(EventMessage value) if (!ValidateMessage(msg)) return; - var zone = _zoneManager.GetZone(msg.SourceZone); - var targetDestination = TryGetValidDestination(msg.RiftConfig); - if (targetDestination == null) - return; - - var zoneTarget = _zoneManager.GetZone(targetDestination.ZoneId); - var targetPos = targetDestination.GetPosition(zoneTarget); - var rift = (TargettedPortal)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.TARGETTED_RIFT); - rift.AddToZone(zone, msg.SourcePosition, ZoneEnterType.NpcSpawn); - rift.SetTarget(zoneTarget, targetPos); - rift.SetConfig(msg.RiftConfig); - Logger.Info(string.Format("TargettedRift spawned on zone {0} {1} ({2})", zone.Id, rift.ED.Name, rift.CurrentPosition)); + CustomRiftSpawner.TrySpawnRift(msg.RiftConfig, _zoneManager, msg.SourceZone, msg.SourcePosition, () => + { + return (StrongholdEntryRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.TARGETTED_RIFT); + }); } } - } } diff --git a/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs index 7891eb7ae..4874ae490 100644 --- a/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs +++ b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs @@ -1,6 +1,7 @@ using Perpetuum.Collections; using Perpetuum.Data; using Perpetuum.ExportedTypes; +using Perpetuum.Log; using Perpetuum.Zones; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Terrains; @@ -44,7 +45,8 @@ public CustomRiftConfigReader() private WeightedCollection GetDestinations(int destinationGroupId) { var group = Db.Query().CommandText( - @"SELECT id, groupId, zoneId, x, y, weight FROM riftdestinations WHERE groupId=@groupId;") + @"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) => @@ -85,6 +87,36 @@ public CustomRiftConfig GetById(int 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.AddToZone(zone, sourcePosition, ZoneEnterType.NpcSpawn); + rift.SetTarget(zoneTarget, targetPos); + rift.SetConfig(config); + + 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; } @@ -107,14 +139,15 @@ public CustomRiftConfig(int id, string name, WeightedCollection des public bool IsDespawning { get { return !Lifespan.Equals(TimeSpan.Zero); } } public bool InfiniteUses { get { return MaxUses < 0; } } - public Destination GetDestination() + public bool IsExcluded(CategoryFlags category) { - return _destinations.GetRandom(); + return category.IsAny(ExcludeClasses); } - public bool IsExcluded(CategoryFlags category) + [CanBeNull] + public Destination GetDestination() { - return category.IsAny(ExcludeClasses); + return _destinations.GetRandom(); } } diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index 46ed6bf1e..5a0fe787b 100644 --- a/src/Perpetuum/Services/RiftSystem/Rift.cs +++ b/src/Perpetuum/Services/RiftSystem/Rift.cs @@ -16,6 +16,17 @@ namespace Perpetuum.Services.RiftSystem { + 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 /// @@ -32,7 +43,7 @@ public virtual void UseItem(Player player) /// /// Rifts that despawn /// - public abstract class DespawningPortal : Portal + public abstract class DespawningPortal : Portal, IDespawningPortal { private UnitDespawnHelper _despawnHelper; @@ -48,15 +59,18 @@ protected override void OnUpdate(TimeSpan time) } } - public class TargettedPortal : DespawningPortal + /// + /// 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 readonly ITeleportStrategyFactories _teleportStrategyFactories; private int uses = 0; - public TargettedPortal(ITeleportStrategyFactories teleportStrategyFactories) + public DespawningTargettedPortal(ITeleportStrategyFactories teleportStrategyFactories) { _teleportStrategyFactories = teleportStrategyFactories; } @@ -64,10 +78,9 @@ public TargettedPortal(ITeleportStrategyFactories 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) @@ -76,6 +89,8 @@ public void SetTarget(IZone zone, Position position) TargetPosition = position; } + private bool HasTarget { get { return TargetZone != null && !TargetPosition.Equals(Position.Empty); } } + private bool IsUsageExceeded() { return !RiftConfig.InfiniteUses && RiftConfig.MaxUses < uses; @@ -94,6 +109,7 @@ private bool IsExcluded(Player player) public override void UseItem(Player player) { + HasTarget.ThrowIfFalse(ErrorCodes.NoTeleportDestinations); base.UseItem(player); IsUsageExceeded().ThrowIfTrue(ErrorCodes.MaximumAllowedRegistrationExceeded); @@ -111,6 +127,16 @@ public override void UseItem(Player player) } } + 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) /// diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs index 9d15044d1..6eafd17d0 100644 --- a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitLocations.cs @@ -15,16 +15,18 @@ public static class StrongholdRiftLocationRepository /// /// IZone /// IEnumerable of StrongholdRiftLocation - public static IEnumerable GetAllInZone(IZone zone) + public static IEnumerable GetAllInZone(IZone zone, ICustomRiftConfigReader customRiftConfigReader) { - var locations = Db.Query().CommandText("SELECT id, zoneid, x, y FROM strongholdexitconfig WHERE zoneid = @zoneId") + 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"); - return new StrongholdRiftLocation(zone, new Position(x, y)); + var riftConfigId = record.GetValue("riftConfigId"); + var riftConfig = customRiftConfigReader.GetById(riftConfigId); + return new StrongholdRiftLocation(zone, new Position(x, y), riftConfig); }); return locations; @@ -38,11 +40,13 @@ 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) + public StrongholdRiftLocation(IZone zone, Position location, CustomRiftConfig riftConfig) { Zone = zone; Location = location; + RiftConfig = riftConfig; } } } diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs deleted file mode 100644 index 0c45b0ecd..000000000 --- a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdExitRift.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Perpetuum.Players; -using Perpetuum.Units.DockingBases; -using Perpetuum.Zones; -using Perpetuum.Zones.Teleporting.Strategies; -using System.Linq; - -namespace Perpetuum.Services.RiftSystem.StrongholdRifts -{ - /// - /// Special Rift to provide exit-jump home but not behave like other Rifts - /// Entitydefault configured to be invulnerable and not despawn - /// - public class StrongholdExitRift : Portal - { - private readonly ITeleportStrategyFactories _teleportStrategyFactories; - private readonly IZone _destinationZone; - private DockingBase _base; - - public StrongholdExitRift(ITeleportStrategyFactories teleportStrategyFactories, IZoneManager zoneManager) - { - _teleportStrategyFactories = teleportStrategyFactories; - _destinationZone = zoneManager.GetZone(8); - } - - protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) - { - _base = _destinationZone.Units.OfType().First(); - base.OnEnterZone(zone, enterType); - } - - public override void UseItem(Player player) - { - base.UseItem(player); - - var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(_destinationZone); - teleport.TargetPosition = UndockSpawnPositionSelector.SelectSpawnPosition(_base); - teleport.DoTeleportAsync(player); - } - } -} diff --git a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs index 935623b84..25439c693 100644 --- a/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/StrongholdRifts/StrongholdRiftManager.cs @@ -1,6 +1,5 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Log; using Perpetuum.Zones; using System; using System.Collections.Generic; @@ -13,14 +12,16 @@ namespace Perpetuum.Services.RiftSystem.StrongholdRifts 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) + public StrongholdRiftManager(IZone zone, IEntityServices entityServices, ICustomRiftConfigReader customRiftConfigReader, IZoneManager zoneManager) { _zone = zone; - _spawnLocations = StrongholdRiftLocationRepository.GetAllInZone(zone); + _spawnLocations = StrongholdRiftLocationRepository.GetAllInZone(zone, customRiftConfigReader); _entityServices = entityServices; + _zoneManager = zoneManager; } private bool _spawnedAll = false; @@ -46,10 +47,10 @@ private void SpawnAll() private void SpawnRift(StrongholdRiftLocation spawn) { - var rift = (StrongholdExitRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.STRONGHOLD_EXIT_RIFT); - rift.AddToZone(_zone, spawn.Location, ZoneEnterType.NpcSpawn); - Logger.Info(string.Format("Rift spawned on zone {0} {1} ({2})", _zone.Id, rift.ED.Name, rift.CurrentPosition)); + CustomRiftSpawner.TrySpawnRift(spawn.RiftConfig, _zoneManager, _zone.Id, spawn.Location, () => + { + return (StrongholdExitRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.STRONGHOLD_EXIT_RIFT); + }); } } - } From 324a449053cbe129760155eed93b7728b169e5ac Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 17 Apr 2021 14:27:20 -0400 Subject: [PATCH 269/457] Container depth check (#302) Avoid inf loop on recursive or cyclic parented entities (should be prevented elsewhere) --- .../Zone/Containers/ZoneContainerRequestHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs index 39a404787..55287740c 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneContainerRequestHandler.cs @@ -57,19 +57,23 @@ protected static ErrorCodes CheckFieldTerminalRange(Player player, Container con 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) + while (fieldTerminal == null && depth < maxDepth) { container = container.ParentEntity; if(container == null) break; fieldTerminal = container.ParentEntity as FieldTerminal; + depth++; } return fieldTerminal; } From bd655de5cb88341c5b4879b3a41e2bd5330fa4d1 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 2 May 2021 21:18:01 -0400 Subject: [PATCH 270/457] Flux mining skill and modifiers (#319) --- src/Perpetuum.ExportedTypes/AggregateField.cs | 2 ++ src/Perpetuum.ExportedTypes/ExtensionNames.cs | 2 ++ src/Perpetuum/Modules/MiningAmmo.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 48aa9fe52..73019d421 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -305,6 +305,7 @@ public enum AggregateField 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, @@ -314,6 +315,7 @@ public enum AggregateField 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, 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/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)}, From ada5fa91b5ee5cc5fc2dfa70e9613481b7d8427d Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 2 May 2021 21:18:37 -0400 Subject: [PATCH 271/457] Stronghold timelimit and anti-camp mechanics (#303) --- .../PerpetuumBootstrapper.cs | 18 +++ src/Perpetuum.ExportedTypes/EffectType.cs | 3 +- src/Perpetuum/Keywords.cs | 1 + src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Players/Player.cs | 21 ++- .../StrongholdPlayerStateManager.cs | 128 ++++++++++++++++++ src/Perpetuum/Units/UnitDespawnHelper.cs | 14 +- src/Perpetuum/Zones/IZone.cs | 1 + src/Perpetuum/Zones/Zone.cs | 10 ++ src/Perpetuum/Zones/ZoneConfiguration.cs | 4 + 10 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index f88a9e359..5bea835ed 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -182,6 +182,7 @@ using TrashItems = Perpetuum.RequestHandlers.TrashItems; using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; +using Perpetuum.Services.Strongholds; namespace Perpetuum.Bootstrapper { @@ -2550,6 +2551,18 @@ void RegisterZone(ZoneType type) where T:Zone _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType().As(); + + + _builder.Register>(x => + { + var ctx = x.Resolve(); + return zone => + { + return new StrongholdPlayerStateManager(zone); + }; + }); + _builder.Register>(x => { var ctx = x.Resolve(); @@ -2583,6 +2596,11 @@ 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())); var listener = ctx.Resolve>().Invoke(zone); listener.Subscribe(zone.Weather); diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index a1559eb69..9c63c34a2 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -114,6 +114,7 @@ public enum EffectType effect_night_clear = 111, effect_night_overcast = 112, effect_weather_good = 113, - effect_weather_bad = 114 + effect_weather_bad = 114, + effect_stronghold_despawn_timer = 115 } } diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index 03c237624..db7665931 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1007,6 +1007,7 @@ public class k 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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index c9ef33148..a009cc45e 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -561,6 +561,7 @@ + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 11f13aabf..cf3f01723 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -25,6 +25,7 @@ using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Services.MissionEngine.TransportAssignments; +using Perpetuum.Services.Strongholds; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Units.DockingBases; @@ -137,6 +138,7 @@ public sealed class Player : Robot,IBlobableUnit,IBlobEmitter private readonly PlayerMovement _movement; private CombatLogger _combatLogger; private PlayerMoveCheckQueue _check; + private StrongholdPlayerDespawnHelper _despawnHelper; public Player(IExtensionReader extensionReader, ICorporationManager corporationManager, @@ -251,7 +253,7 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) MissionHandler = _missionHandlerFactory(zone, this); MissionHandler.InitMissions(); - Direction = FastRandom.NextDouble(); + Direction = FastRandom.NextDouble(); var p = DynamicProperties.GetProperty(k.pvpRemaining); if (!p.HasValue) @@ -320,6 +322,22 @@ protected override void OnUpdate(TimeSpan time) MissionHandler.Update(time); _combatLogger?.Update(time); + _despawnHelper?.Update(time, this); + } + + public void SetStrongholdDespawn(TimeSpan time, UnitDespawnStrategy strategy) + { + if (_despawnHelper == null) + { + _despawnHelper = StrongholdPlayerDespawnHelper.Create(this, time); + _despawnHelper.DespawnStrategy = strategy; + } + } + + public void ClearStrongholdDespawn() + { + _despawnHelper?.Cancel(this); + _despawnHelper = null; } public void SendModuleProcessError(Module module, ErrorCodes error) @@ -346,7 +364,6 @@ public void RemoveInvulnerableEffect() EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); } - public void ApplyTeleportSicknessEffect() { var zone = Zone; diff --git a/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs new file mode 100644 index 000000000..fd3865a96 --- /dev/null +++ b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs @@ -0,0 +1,128 @@ +using Perpetuum.Data; +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Units; +using Perpetuum.Zones; +using System; + +namespace Perpetuum.Services.Strongholds +{ + public interface IStrongholdPlayerStateManager + { + void OnPlayerEnterZone(Player player); + void OnPlayerExitZone(Player player); + } + + public class StrongholdPlayerStateManager : IStrongholdPlayerStateManager + { + private readonly TimeSpan MAX = TimeSpan.FromMinutes(60); + private readonly TimeSpan MIN = TimeSpan.FromSeconds(30); + private readonly IZone _zone; + + public StrongholdPlayerStateManager(IZone zone) + { + _zone = zone; + MAX = TimeSpan.FromMinutes(_zone.Configuration.TimeLimitMinutes ?? 60); + } + + public void OnPlayerEnterZone(Player player) + { + var now = DateTime.UtcNow; + var effectEnd = player.DynamicProperties.GetOrAdd(k.strongholdDespawnTime, now.Add(MAX)); + var effectDuration = (effectEnd - now).Max(MIN); + ApplyDespawn(player, effectDuration, effectEnd); + } + + public void OnPlayerExitZone(Player player) + { + player.ClearStrongholdDespawn(); + player.DynamicProperties.Remove(k.strongholdDespawnTime); + } + + private void ApplyDespawn(Player player, TimeSpan remaining, DateTime endTime) + { + using (var scope = Db.CreateTransaction()) + { + player.DynamicProperties.Update(k.strongholdDespawnTime, endTime); + player.Save(); + scope.Complete(); + } + player.SetStrongholdDespawn(remaining, (u) => + { + using (var scope = Db.CreateTransaction()) + { + if (u is Player p) + { + var dockingBase = p.Character.GetHomeBaseOrCurrentBase(); + p.DockToBase(dockingBase.Zone, dockingBase); + p.DynamicProperties.Remove(k.strongholdDespawnTime); + p.Save(); + } + scope.Complete(); + } + }); + } + } + + public class StrongholdPlayerDespawnHelper : UnitDespawnHelper + { + private static readonly EffectType DespawnEffect = EffectType.effect_stronghold_despawn_timer; + + private StrongholdPlayerDespawnHelper(TimeSpan despawnTime) : base(despawnTime) { } + + private bool _canceled = false; + public void Cancel(Unit unit) + { + _canceled = true; + RemoveDespawnEffect(unit); + } + + public bool HasEffect(Unit unit) + { + return unit.EffectHandler.ContainsToken(_effectToken); + } + + private bool _detectedEffectApplied = false; + private bool EffectLive(Unit unit) + { + var effectRunning = HasEffect(unit); + if (!_detectedEffectApplied) + { + _detectedEffectApplied = effectRunning; + } + return _detectedEffectApplied == effectRunning; + } + + public override void Update(TimeSpan time, Unit unit) + { + _timer.Update(time).IsPassed(() => + { + if (_canceled || EffectLive(unit)) + return; + + DespawnStrategy?.Invoke(unit); + }); + } + + private void RemoveDespawnEffect(Unit unit) + { + unit.EffectHandler.RemoveEffectByToken(_effectToken); + } + + private void ApplyDespawnEffect(Unit unit) + { + var effectBuilder = unit.NewEffectBuilder().SetType(DespawnEffect).WithDuration(_despawnTime).WithToken(_effectToken); + unit.ApplyEffect(effectBuilder); + } + + public new static StrongholdPlayerDespawnHelper Create(Unit unit, TimeSpan despawnTime) + { + var helper = new StrongholdPlayerDespawnHelper(despawnTime); + helper.ApplyDespawnEffect(unit); + return helper; + } + } +} + + + diff --git a/src/Perpetuum/Units/UnitDespawnHelper.cs b/src/Perpetuum/Units/UnitDespawnHelper.cs index 46779613d..20384baa7 100644 --- a/src/Perpetuum/Units/UnitDespawnHelper.cs +++ b/src/Perpetuum/Units/UnitDespawnHelper.cs @@ -10,19 +10,19 @@ namespace Perpetuum.Units public class UnitDespawnHelper { - private readonly TimeSpan _despawnTime; - private readonly IntervalTimer _timer = new IntervalTimer(500); + protected readonly TimeSpan _despawnTime; + protected readonly IntervalTimer _timer = new IntervalTimer(650); - private UnitDespawnHelper(TimeSpan despawnTime) + protected UnitDespawnHelper(TimeSpan despawnTime) { _despawnTime = despawnTime; } - public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { private get; set; } + public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { protected get; set; } - public UnitDespawnStrategy DespawnStrategy { private get; set; } + public UnitDespawnStrategy DespawnStrategy { protected get; set; } - public void Update(TimeSpan time,Unit unit) + public virtual void Update(TimeSpan time,Unit unit) { _timer.Update(time).IsPassed(() => { @@ -44,7 +44,7 @@ public void Update(TimeSpan time,Unit unit) }); } - private readonly EffectToken _effectToken = EffectToken.NewToken(); + protected readonly EffectToken _effectToken = EffectToken.NewToken(); private void TryReApplyDespawnEffect(Unit unit) { diff --git a/src/Perpetuum/Zones/IZone.cs b/src/Perpetuum/Zones/IZone.cs index a2553a832..edad57087 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -6,6 +6,7 @@ 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; diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 07451e3bb..4d0140403 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -18,6 +18,7 @@ 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; @@ -73,6 +74,9 @@ public abstract class Zone : Threading.Process.Process, IZone [CanBeNull] public IRelicManager RelicManager { get; set; } + [CanBeNull] + public IStrongholdPlayerStateManager PlayerStateManager { get; set; } + public IZoneEnterQueueService EnterQueueService { get; set; } public IHighScoreService HighScores { get; set; } @@ -201,7 +205,10 @@ public void AddUnit(Unit unit) return; if (unit is Player player) + { ImmutableInterlocked.TryAdd(ref _players, player.Eid, player); + PlayerStateManager?.OnPlayerEnterZone(player); + } unit.Updated += OnUnitUpdated; unit.Dead += OnUnitDead; @@ -234,7 +241,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})"); diff --git a/src/Perpetuum/Zones/ZoneConfiguration.cs b/src/Perpetuum/Zones/ZoneConfiguration.cs index ec27cb78a..2a4155e31 100644 --- a/src/Perpetuum/Zones/ZoneConfiguration.cs +++ b/src/Perpetuum/Zones/ZoneConfiguration.cs @@ -62,6 +62,8 @@ public IEnumerable GetAll() MaxDockingBase = record.GetValue("maxdockingbase"), PlantAltitudeScale = record.GetValue("plantaltitudescale"), Note = record.GetValue("note"), + + TimeLimitMinutes = record.GetValue("timeLimitMinutes") }; config.PlantRules = _plantRuleLoader.LoadPlantRulesWithOverrides(config.plantRuleSetId); @@ -104,6 +106,8 @@ public sealed class ZoneConfiguration public string ListenerAddress { get; set; } public int ListenerPort { get; set; } + public int? TimeLimitMinutes { get; set; } + public ZoneType Type { get; set; } public Dictionary ToDictionary() From a780e3b928a8b6d0d0715f604582750f26f3b0bd Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 2 May 2021 21:19:08 -0400 Subject: [PATCH 272/457] Eventservice optimizations (#307) --- .../PerpetuumBootstrapper.cs | 4 --- src/Perpetuum/Perpetuum.csproj | 1 + .../EventServices/EventListenerService.cs | 30 +++++++++++++------ .../EventMessages/EventMessage.cs | 6 ++-- .../EventMessages/EventMessageSimple.cs | 5 ++-- .../EventMessages/GameTimeMessage.cs | 3 +- .../EventMessages/NpcEventMessage.cs | 3 +- .../EventMessages/NpcReinforcementsMessage.cs | 4 ++- .../EventMessages/OreNpcSpawnMessage.cs | 4 ++- .../EventMessages/SpawnPortalMessage.cs | 4 ++- .../EventMessages/StabilityEffectMessage.cs | 4 ++- .../EventMessages/WeatherEventMessage.cs | 3 +- .../EventProcessors/AffectOutpostStability.cs | 6 ++-- .../EventServices/EventProcessors/ChatEcho.cs | 10 ++++--- .../EnvironmentalEffectHandler.cs | 7 +++-- .../EventProcessors/EventProcessor.cs | 11 ++++--- .../NpcReinforcementSpawner.cs | 4 ++- .../NpcSpawnEventHandler.cs | 6 ++-- .../NpcSpawnEventHandlers/OreNPCSpawner.cs | 4 ++- .../EventProcessors/PortalSpawner.cs | 4 ++- .../Services/EventServices/EventType.cs | 14 +++++++++ src/Perpetuum/Zones/Intrusion/Outpost.cs | 8 ++++- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 2 +- 23 files changed, 99 insertions(+), 48 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventType.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 5bea835ed..381f02db4 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -861,10 +861,6 @@ private void RegisterEntities() RegisterUnit(); RegisterUnit().OnActivated(e => { - var listener = new AffectOutpostStability(e.Instance); - e.Context.Resolve().AttachListener(listener); - - #if (DEBUG) var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromSeconds(10),TimeSpan.FromSeconds(15)); #else diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index a009cc45e..6a8e77c82 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -296,6 +296,7 @@ + diff --git a/src/Perpetuum/Services/EventServices/EventListenerService.cs b/src/Perpetuum/Services/EventServices/EventListenerService.cs index 7959688a3..7c13617fb 100644 --- a/src/Perpetuum/Services/EventServices/EventListenerService.cs +++ b/src/Perpetuum/Services/EventServices/EventListenerService.cs @@ -14,31 +14,34 @@ namespace Perpetuum.Services.EventServices public class EventListenerService : Process { private readonly object _lock = new object(); - private readonly IList _observers; - private readonly ConcurrentQueue _queue; + private readonly IDictionary> _observers; + private readonly ConcurrentQueue _queue; public EventListenerService() { - _observers = new List(); - _queue = new ConcurrentQueue(); + _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(EventMessage message) + public void PublishMessage(IEventMessage message) { _queue.Enqueue(message); } - public void NotifyListeners(EventMessage message) + public void NotifyListeners(IEventMessage message) { lock (_lock) { - foreach (var obs in _observers) + if(_observers.TryGetValue(message.Type, out var list)) { - obs.OnNext(message); + foreach (var obs in list) + { + obs.OnNext(message); + } } } } @@ -50,7 +53,16 @@ public void NotifyListeners(EventMessage message) public void AttachListener(IEventProcessor observer) { lock (_lock) - _observers.Add(observer); + { + if (!_observers.ContainsKey(observer.Type)) + { + _observers.Add(observer.Type, new List() { observer }); + } + else + { + _observers[observer.Type].Add(observer); + } + } } public override void Update(TimeSpan time) diff --git a/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs index a245269ea..d0511e787 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/EventMessage.cs @@ -1,9 +1,7 @@ namespace Perpetuum.Services.EventServices.EventMessages { - /// - /// A specification Interface for EventMessages - /// - public interface EventMessage + public interface IEventMessage { + EventType Type { get; } } } diff --git a/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs b/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs index 7a05419c0..7ae28808f 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/EventMessageSimple.cs @@ -3,15 +3,14 @@ /// /// A Simple EventMessage stub with string content, mostly to demonstrate usage /// - public class EventMessageSimple : EventMessage + 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 index f9e3c4d20..bd5fff3cf 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/GameTimeMessage.cs @@ -2,8 +2,9 @@ namespace Perpetuum.Services.EventServices.EventMessages { - public class GameTimeMessage : EventMessage + public class GameTimeMessage : IEventMessage { + public EventType Type => EventType.Environmental; public GameTimeInfo TimeInfo { get; private set; } public GameTimeMessage(GameTimeInfo time) { diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs index c2a04f7c6..3ca039c9f 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcEventMessage.cs @@ -7,8 +7,9 @@ namespace Perpetuum.Services.EventServices.EventMessages /// /// EventMessage sent by an NPC /// - public class NpcMessage : EventMessage + public class NpcMessage : IEventMessage { + public EventType Type => EventType.NpcChat; private string _content; private readonly Unit _source; diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs index 29c52472d..2f85f73ed 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs @@ -2,8 +2,10 @@ namespace Perpetuum.Services.EventServices.EventMessages { - public class NpcReinforcementsMessage : EventMessage + public class NpcReinforcementsMessage : IEventMessage { + public EventType Type => EventType.NpcReinforce; + public Npc Npc { get; } public int ZoneId { get; } diff --git a/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs index 603c85138..28612363d 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/OreNpcSpawnMessage.cs @@ -8,8 +8,10 @@ public enum OreNodeState Removed } - public class OreNpcSpawnMessage : EventMessage + public class OreNpcSpawnMessage : IEventMessage { + public EventType Type => EventType.NpcOre; + public OreNodeState NodeState { get; } public MineralNode Node { get; } public int ZoneId { get; } diff --git a/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs index f6b4a6cf0..94c98d21b 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs @@ -2,8 +2,10 @@ namespace Perpetuum.Services.EventServices.EventMessages { - public class SpawnPortalMessage : EventMessage + 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; } diff --git a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs index b6c00cefc..c47059f2d 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/StabilityEffectMessage.cs @@ -8,8 +8,10 @@ namespace Perpetuum.Services.EventServices.EventMessages /// /// An EventMessage of an event to alter Outpost stability (SAP) /// - public class StabilityAffectingEvent : EventMessage + 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; } diff --git a/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs index 5dbdd851b..96a6d6a0b 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/WeatherEventMessage.cs @@ -2,8 +2,9 @@ namespace Perpetuum.Services.EventServices.EventMessages { - public class WeatherEventMessage : EventMessage + 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) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs index 45379fb56..8ca71f38f 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/AffectOutpostStability.cs @@ -8,13 +8,15 @@ namespace Perpetuum.Services.EventServices.EventProcessors /// public class AffectOutpostStability : EventProcessor { - private Outpost _outpost; + private readonly Outpost _outpost; public AffectOutpostStability(Outpost outpost) { _outpost = outpost; } - public override void HandleMessage(EventMessage value) + public override EventType Type => EventType.OutpostStability; + + public override void HandleMessage(IEventMessage value) { if (value is StabilityAffectingEvent msg) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs index 809e056b7..946ff0436 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs @@ -14,7 +14,7 @@ public class ChatEcho : EventProcessor { private readonly IChannelManager _channelManager; private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; - private Character _announcer; + private readonly Character _announcer; public ChatEcho(IChannelManager channelManager) { @@ -22,7 +22,8 @@ public ChatEcho(IChannelManager channelManager) _channelManager = channelManager; } - public override void HandleMessage(EventMessage value) + public override EventType Type => EventType.undefined; + public override void HandleMessage(IEventMessage value) { if (value is EventMessageSimple msg) { @@ -40,14 +41,15 @@ public override void HandleMessage(EventMessage value) public class NpcChatEcho : EventProcessor { private const string SENDER_CHARACTER_NICKNAME = "[OPP] Announcer"; //TODO "Nian" character - private Character _announcer; + private readonly Character _announcer; public NpcChatEcho() { _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); } - public override void HandleMessage(EventMessage value) + public override EventType Type => EventType.NpcChat; + public override void HandleMessage(IEventMessage value) { if (value is NpcMessage msg) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs index 4b502437c..61391902d 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EnvironmentalEffectHandler.cs @@ -71,7 +71,7 @@ private void OnStateChange() } } - private bool TryGetWeatherMessage(EventMessage value) + private bool TryGetWeatherMessage(IEventMessage value) { var msg = value as WeatherEventMessage; var isValidMsg = msg != null && msg.ZoneId == _zone.Id; @@ -82,7 +82,7 @@ private bool TryGetWeatherMessage(EventMessage value) return isValidMsg; } - private bool TryGetGameTimeMessage(EventMessage value) + private bool TryGetGameTimeMessage(IEventMessage value) { var msg = value as GameTimeMessage; var isValidMsg = msg != null; @@ -93,7 +93,8 @@ private bool TryGetGameTimeMessage(EventMessage value) return isValidMsg; } - public override void HandleMessage(EventMessage value) + public override EventType Type => EventType.Environmental; + public override void HandleMessage(IEventMessage value) { var stateChange = TryGetWeatherMessage(value) || TryGetGameTimeMessage(value); if (stateChange) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs index 464dc98a7..bf90eee8a 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/EventProcessor.cs @@ -3,16 +3,19 @@ namespace Perpetuum.Services.EventServices.EventProcessors { - public interface IEventProcessor : IObserver + public interface IEventProcessor : IObserver { - void HandleMessage(EventMessage value); + void HandleMessage(IEventMessage value); + EventType Type { get; } } public abstract class EventProcessor : IEventProcessor { - public abstract void HandleMessage(EventMessage value); + public abstract EventType Type { get; } - public void OnNext(EventMessage value) + public abstract void HandleMessage(IEventMessage value); + + public void OnNext(IEventMessage value) { HandleMessage(value); } diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs index 7a2b92e47..c9aef10ff 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -18,6 +18,8 @@ public class NpcReinforcementSpawner : NpcSpawnEventHandler EventType.NpcReinforce; + private readonly IDictionary _reinforcementsByNpc = new Dictionary(); public NpcReinforcementSpawner(IZone zone, INpcReinforcementsRepository reinforcementsRepo) : base(zone, reinforcementsRepo) { } @@ -26,7 +28,7 @@ protected override IEnumerable GetActiveReinforcments(Presen return _reinforcementsByNpc.Where(p => p.Value.HasActivePresence(presence)).Select(p => p.Value); } - protected override bool CheckMessage(EventMessage inMsg, out NpcReinforcementsMessage msg) + protected override bool CheckMessage(IEventMessage inMsg, out NpcReinforcementsMessage msg) { if (inMsg is NpcReinforcementsMessage message && _zone.Id == message.ZoneId) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs index 10d394b47..f89c735a5 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcSpawnEventHandler.cs @@ -14,7 +14,7 @@ namespace Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers /// Common functions and event handling procedure for NpcReinforcement related events /// /// EventMessage - public abstract class NpcSpawnEventHandler : EventProcessor where T : EventMessage + public abstract class NpcSpawnEventHandler : EventProcessor where T : IEventMessage { protected abstract TimeSpan SPAWN_DELAY { get; } protected abstract TimeSpan SPAWN_LIFETIME { get; } @@ -30,7 +30,7 @@ public NpcSpawnEventHandler(IZone zone, INpcReinforcementsRepository reinforceme protected abstract IEnumerable GetActiveReinforcments(Presence presence); - protected abstract bool CheckMessage(EventMessage inMsg, out T msg); + protected abstract bool CheckMessage(IEventMessage inMsg, out T msg); protected abstract void CheckReinforcements(T msg); @@ -81,7 +81,7 @@ protected void ExpireWave(INpcReinforcementWave wave) private bool _spawning = false; - public override void HandleMessage(EventMessage value) + public override void HandleMessage(IEventMessage value) { if (CheckMessage(value, out T msg)) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs index 87fe22868..342f3edbb 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/OreNPCSpawner.cs @@ -21,6 +21,8 @@ public class OreNpcSpawner : NpcSpawnEventHandler private const int MIN_SPAWN_DIST_TOLERANCE = 30; + public override EventType Type => EventType.NpcOre; + private readonly IDictionary _reinforcementsByNode = new Dictionary(); private readonly IEnumerable _mineralConfigs; @@ -34,7 +36,7 @@ protected override IEnumerable GetActiveReinforcments(Presen return _reinforcementsByNode.Where(p => p.Value.HasActivePresence(presence)).Select(p => p.Value); } - protected override bool CheckMessage(EventMessage inMsg, out OreNpcSpawnMessage msg) + protected override bool CheckMessage(IEventMessage inMsg, out OreNpcSpawnMessage msg) { if (inMsg is OreNpcSpawnMessage message && _zone.Id == message.ZoneId) { diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs index 4f4fd03fd..7a14d797d 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs @@ -11,6 +11,8 @@ 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; @@ -26,7 +28,7 @@ private bool ValidateMessage(SpawnPortalMessage msg) return true; } - public override void HandleMessage(EventMessage value) + public override void HandleMessage(IEventMessage value) { if (value is SpawnPortalMessage msg) { diff --git a/src/Perpetuum/Services/EventServices/EventType.cs b/src/Perpetuum/Services/EventServices/EventType.cs new file mode 100644 index 000000000..0d2fd60e5 --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventType.cs @@ -0,0 +1,14 @@ +namespace Perpetuum.Services.EventServices +{ + public enum EventType + { + undefined, + ChatEcho, + OutpostStability, + NpcChat, + NpcOre, + NpcReinforce, + Environmental, + PortalSpawn, + } +} diff --git a/src/Perpetuum/Zones/Intrusion/Outpost.cs b/src/Perpetuum/Zones/Intrusion/Outpost.cs index 595ae891b..807f185a0 100644 --- a/src/Perpetuum/Zones/Intrusion/Outpost.cs +++ b/src/Perpetuum/Zones/Intrusion/Outpost.cs @@ -17,8 +17,8 @@ using Perpetuum.Services.Channels; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; +using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Services.Looting; -using Perpetuum.Services.Relics; using Perpetuum.Timers; using Perpetuum.Units.DockingBases; using Perpetuum.Zones.Effects; @@ -78,6 +78,11 @@ public Outpost(IEntityServices entityServices, _decay = new OutpostDecay(_eventChannel, this); } + private void InitStabilityListener() + { + _eventChannel.AttachListener(new AffectOutpostStability(this)); + } + public void PublishSAPEvent(StabilityAffectingEvent eventMsg) { _eventChannel.PublishMessage(eventMsg); @@ -311,6 +316,7 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { base.OnEnterZone(zone, enterType); RefreshEffectBonus(); + InitStabilityListener(); } private bool _enabled = true; diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index bb01b5235..6b6fa6321 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -234,7 +234,7 @@ private static void SendMessage(Unit src, EventListenerService eventChannel, str { if (!msg.IsNullOrEmpty()) { - EventMessage eventMessage = new NpcMessage(msg, src); + IEventMessage eventMessage = new NpcMessage(msg, src); Task.Run(() => eventChannel.PublishMessage(eventMessage)); } } From 642b895c5f830930979c3b99fca80e628d30decb Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 16 May 2021 17:41:53 -0400 Subject: [PATCH 273/457] Communicate expiration stronghold (#322) * send message to player pm's about the remaining time on stronghold * bug fix for timer not clearing, nor being island specific, refactors --- .../PerpetuumBootstrapper.cs | 4 +- src/Perpetuum/Keywords.cs | 1 + src/Perpetuum/Perpetuum.csproj | 1 + .../EventMessages/DirectMessage.cs | 19 ++++ .../EventServices/EventProcessors/ChatEcho.cs | 25 +++++ .../Services/EventServices/EventType.cs | 1 + .../StrongholdPlayerStateManager.cs | 94 +++++++++++++++---- src/Perpetuum/TimeSpanExtensions.cs | 22 +++++ src/Perpetuum/Zones/Zone.cs | 2 +- 9 files changed, 149 insertions(+), 20 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/DirectMessage.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 381f02db4..cbedf93f5 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1698,6 +1698,7 @@ private void InitRelayManager() // OPP: EventListenerService and consumers _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); @@ -1707,6 +1708,7 @@ private void InitRelayManager() { 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()); var obs = new GameTimeObserver(e.Instance); @@ -2555,7 +2557,7 @@ void RegisterZone(ZoneType type) where T:Zone var ctx = x.Resolve(); return zone => { - return new StrongholdPlayerStateManager(zone); + return new StrongholdPlayerStateManager(zone, ctx.Resolve()); }; }); diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index db7665931..98fd73323 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -816,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"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 6a8e77c82..d6274b856 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -290,6 +290,7 @@ + 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/EventProcessors/ChatEcho.cs b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs index 946ff0436..80ab7435b 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/ChatEcho.cs @@ -4,6 +4,7 @@ using Perpetuum.Services.EventServices.EventProcessors; using Perpetuum.Units; using Perpetuum.Zones; +using System.Collections.Generic; namespace Perpetuum.Services.EventServices { @@ -29,9 +30,33 @@ public override void HandleMessage(IEventMessage value) { _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(); + } + } } /// diff --git a/src/Perpetuum/Services/EventServices/EventType.cs b/src/Perpetuum/Services/EventServices/EventType.cs index 0d2fd60e5..d630e3428 100644 --- a/src/Perpetuum/Services/EventServices/EventType.cs +++ b/src/Perpetuum/Services/EventServices/EventType.cs @@ -4,6 +4,7 @@ public enum EventType { undefined, ChatEcho, + DMEcho, OutpostStability, NpcChat, NpcOre, diff --git a/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs index fd3865a96..f0d5e3af3 100644 --- a/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs +++ b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs @@ -1,6 +1,8 @@ using Perpetuum.Data; using Perpetuum.ExportedTypes; using Perpetuum.Players; +using Perpetuum.Services.EventServices; +using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Units; using Perpetuum.Zones; using System; @@ -15,53 +17,109 @@ public interface IStrongholdPlayerStateManager 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 IZone _zone; - public StrongholdPlayerStateManager(IZone zone) + 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(); - player.DynamicProperties.Remove(k.strongholdDespawnTime); } private void ApplyDespawn(Player player, TimeSpan remaining, DateTime endTime) { - using (var scope = Db.CreateTransaction()) - { - player.DynamicProperties.Update(k.strongholdDespawnTime, endTime); - player.Save(); - scope.Complete(); - } + player.DynamicProperties.Update(k.strongholdDespawnTime, endTime); player.SetStrongholdDespawn(remaining, (u) => { - using (var scope = Db.CreateTransaction()) + if (u is Player p) { - if (u is Player p) - { - var dockingBase = p.Character.GetHomeBaseOrCurrentBase(); - p.DockToBase(dockingBase.Zone, dockingBase); - p.DynamicProperties.Remove(k.strongholdDespawnTime); - p.Save(); - } - scope.Complete(); + 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)); + } } public class StrongholdPlayerDespawnHelper : UnitDespawnHelper 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/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 4d0140403..91f16b1cc 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -207,7 +207,7 @@ public void AddUnit(Unit unit) if (unit is Player player) { ImmutableInterlocked.TryAdd(ref _players, player.Eid, player); - PlayerStateManager?.OnPlayerEnterZone(player); + StrongholdPlayerStateManager.OnPlayerAddToZone(this, player); } unit.Updated += OnUnitUpdated; From 980fa62ff2c4400c06c2714ae39db29fa896e648 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 22 May 2021 16:14:38 -0400 Subject: [PATCH 274/457] Boss announcer (#323) * wip on boss announce channel feature * overhaul motd, handle character/channel issue, deal with delayed messages being outofdate, better motd string building --- .../PerpetuumBootstrapper.cs | 2 + .../Accounting/Characters/Character.cs | 5 + src/Perpetuum/Perpetuum.csproj | 2 + src/Perpetuum/Services/Channels/Channel.cs | 2 +- .../EventMessages/NpcStateMessage.cs | 27 ++++ .../EventProcessors/NpcStateAnnouncer.cs | 153 ++++++++++++++++++ .../Services/EventServices/EventType.cs | 1 + .../Flocks/FlockConfigurationRepository.cs | 2 +- .../Zones/NpcSystem/Flocks/NormalFlock.cs | 5 +- src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 115 +++++++++---- src/Perpetuum/Zones/NpcSystem/Npc.cs | 28 +--- 11 files changed, 280 insertions(+), 62 deletions(-) create mode 100644 src/Perpetuum/Services/EventServices/EventMessages/NpcStateMessage.cs create mode 100644 src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index cbedf93f5..51c8664f9 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1702,6 +1702,7 @@ private void InitRelayManager() _builder.RegisterType(); _builder.RegisterType(); _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType().As>(); _builder.RegisterType().As>(); _builder.RegisterType().SingleInstance().OnActivated(e => @@ -1711,6 +1712,7 @@ private void InitRelayManager() e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); + e.Instance.AttachListener(e.Context.Resolve()); var obs = new GameTimeObserver(e.Instance); obs.Subscribe(e.Context.Resolve()); }); diff --git a/src/Perpetuum/Accounting/Characters/Character.cs b/src/Perpetuum/Accounting/Characters/Character.cs index f9201fd7b..939ff8312 100644 --- a/src/Perpetuum/Accounting/Characters/Character.cs +++ b/src/Perpetuum/Accounting/Characters/Character.cs @@ -34,6 +34,11 @@ public class Character : IEquatable,IComparable 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; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d6274b856..c77693b54 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -292,10 +292,12 @@ + + diff --git a/src/Perpetuum/Services/Channels/Channel.cs b/src/Perpetuum/Services/Channels/Channel.cs index ac494058b..ce9bac006 100644 --- a/src/Perpetuum/Services/Channels/Channel.cs +++ b/src/Perpetuum/Services/Channels/Channel.cs @@ -171,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); 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/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs new file mode 100644 index 000000000..a411624ce --- /dev/null +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -0,0 +1,153 @@ +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; + +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 = _nameDictionary[nameToken]?.ToString(); + return name ?? string.Empty; + } + + + 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 void UpdateState(NpcStateMessage msg) + { + var defKey = GetNpcDef(msg); + if (defKey < 0) return; + if (!_state.ContainsKey(defKey) || IsUpdatable(_state[defKey], msg)) + { + _state[defKey] = msg; + } + } + + public override EventType Type => EventType.NpcState; + public override void HandleMessage(IEventMessage value) + { + if (value is NpcStateMessage msg) + { + 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/EventType.cs b/src/Perpetuum/Services/EventServices/EventType.cs index d630e3428..85de191c3 100644 --- a/src/Perpetuum/Services/EventServices/EventType.cs +++ b/src/Perpetuum/Services/EventServices/EventType.cs @@ -7,6 +7,7 @@ public enum EventType DMEcho, OutpostStability, NpcChat, + NpcState, NpcOre, NpcReinforce, Environmental, diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs index 0f2fee557..345f8e335 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -42,7 +42,7 @@ public void LoadAllConfig() c.Enabled = r.GetValue("enabled"); c.BehaviorType = (NpcBehaviorType) r.GetValue("behaviorType"); c.SpecialType = (NpcSpecialType) r.GetValue("npcSpecialType"); - c.BossInfo = _bossBuilder.GetBossInfoByFlockID(c.ID); + c.BossInfo = _bossBuilder.GetBossInfoByFlockID(c.ID, c); }); var config = builder.Build(); _flockConfigurations[config.ID] = config; diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index 987583c13..a3899894f 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs @@ -124,10 +124,7 @@ protected override void CreateMemberInZone() if (IsMaxSpawnCountReached) return; - if (IsBoss) - { - BossInfo.OnRespawn(); - } + BossInfo?.OnRespawn(); base.CreateMemberInZone(); } diff --git a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index 6b6fa6321..938c029cb 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -6,6 +6,7 @@ using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Zones.Intrusion; +using Perpetuum.Zones.NpcSystem.Flocks; using System; using System.Collections.Generic; using System.Data; @@ -17,9 +18,11 @@ namespace Perpetuum.Zones.NpcSystem public class NpcBossInfoBuilder { private readonly ICustomRiftConfigReader _customRiftConfigReader; - public NpcBossInfoBuilder(ICustomRiftConfigReader customRiftConfigReader) + private readonly EventListenerService _eventChannel; + public NpcBossInfoBuilder(ICustomRiftConfigReader customRiftConfigReader, EventListenerService eventChannel) { _customRiftConfigReader = customRiftConfigReader; + _eventChannel = eventChannel; } public NpcBossInfo CreateBossInfoFromDB(IDataRecord record) @@ -35,8 +38,10 @@ public NpcBossInfo CreateBossInfoFromDB(IDataRecord record) 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(id, + var info = new NpcBossInfo(_eventChannel, + id, flockid, respawnFactor, lootSplit, @@ -45,22 +50,30 @@ public NpcBossInfo CreateBossInfoFromDB(IDataRecord record) overrideRelations, deathMessage, aggressMessage, - riftConfig + riftConfig, + announce ); return info; } - public NpcBossInfo GetBossInfoByFlockID(int flockid) + 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 + stabilityPts, overrideRelations, customDeathMessage, customAggressMessage, riftConfigId, + isAnnounced FROM dbo.npcbossinfo WHERE flockid=@flockid;") .SetParameter("@flockid", flockid) .Execute() .Select(CreateBossInfoFromDB); - return bossInfos.SingleOrDefault(); + var info = bossInfos.SingleOrDefault(); + if(info == null) + { + return null; + } + info.RespawnTime = config.RespawnTime; + return info; } } @@ -70,6 +83,7 @@ public NpcBossInfo GetBossInfoByFlockID(int flockid) /// public class NpcBossInfo { + private readonly EventListenerService _eventChannel; private readonly int _id; private readonly double? _respawnNoiseFactor; private readonly long? _outpostEID; @@ -80,6 +94,7 @@ public class NpcBossInfo private bool _speak; public int FlockId { get; } + public TimeSpan RespawnTime { get; set; } private bool IsOutpostBoss { get { return _outpostEID != null; } } private int StabilityPoints { get { return _stabilityPts ?? 0; } } @@ -89,8 +104,11 @@ public class NpcBossInfo public bool IsLootSplit { get; } public bool IsDead { get; private set; } - public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSplit, long? outpostEID, int? stabilityPts, bool overrideRelations, string customDeathMsg, string customAggroMsg, CustomRiftConfig riftConfig) + 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; @@ -101,6 +119,7 @@ public NpcBossInfo(int id, int flockid, double? respawnNoiseFactor, bool lootSpl _deathMsg = customDeathMsg; _aggroMsg = customAggroMsg; _riftConfig = riftConfig; + IsAnnounced = announce; _speak = true; IsDead = false; } @@ -117,27 +136,25 @@ public void OnDeAggro() /// Handle any actions that this NPC Boss should do upon Aggression, including sending a message /// /// Player aggressor - /// the npc event channel - public void OnAggro(Player aggressor, EventListenerService channel) + public void OnAggro(Player aggressor) { - CommunicateAggression(aggressor, channel); + CommunicateAggression(aggressor); HandleBossOutpostAggro(aggressor); } // Timer to buffer excessive decrease frequency of messages from OnDamageTaken - private readonly TimeKeeper _time = new TimeKeeper(TimeSpan.FromSeconds(5)); + private readonly TimeKeeper _onDamageDebounce = new TimeKeeper(TimeSpan.FromSeconds(5)); /// /// Handle events to dispatch when the npc boss takes damage /// /// The npc Boss killed /// Player damager - /// npc-event listener channel - public void OnDamageTaken(Npc npc, Player aggressor, EventListenerService channel) + public void OnDamageTaken(Npc npc, Player aggressor) { - if (_time.Expired) + if (_onDamageDebounce.Expired) { - channel.PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); - _time.Reset(); + PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + _onDamageDebounce.Reset(); } } @@ -147,14 +164,14 @@ public void OnDamageTaken(Npc npc, Player aggressor, EventListenerService channe /// /// The npc Boss killed /// Player killer - /// npc-event listener channel - public void OnDeath(Npc npc, Unit killer, EventListenerService channel) + public void OnDeath(Npc npc, Unit killer) { - CommunicateDeath(killer, channel); - HandleBossOutpostDeath(npc, killer, channel); - SpawnPortal(npc, killer, channel); + CommunicateDeath(killer); + HandleBossOutpostDeath(npc, killer); + SpawnPortal(npc, killer); IsDead = true; - channel.PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + AnnounceDeath(); } /// @@ -164,6 +181,28 @@ public void OnRespawn() { _speak = true; IsDead = false; + AnnouceRespawn(); + } + + 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)); } /// @@ -185,21 +224,25 @@ private void HandleBossOutpostAggro(Player aggressor) } } - private void CommunicateAggression(Unit aggressor, EventListenerService channel) + + // Timer to buffer excessive decrease frequency of messages from OnDamageTaken + private readonly TimeKeeper _aggroDebounce = new TimeKeeper(TimeSpan.FromSeconds(5)); + private void CommunicateAggression(Unit aggressor) { - if (_speak) + if (_aggroDebounce.Expired && _speak) { _speak = false; - SendMessage(aggressor, channel, _aggroMsg); + SendMessage(aggressor, _aggroMsg); + _aggroDebounce.Reset(); } } - private void CommunicateDeath(Unit aggressor, EventListenerService channel) + private void CommunicateDeath(Unit aggressor) { - SendMessage(aggressor, channel, _deathMsg); + SendMessage(aggressor, _deathMsg); } - private void HandleBossOutpostDeath(Npc npc, Unit killer, EventListenerService channel) + private void HandleBossOutpostDeath(Npc npc, Unit killer) { if (!IsOutpostBoss) return; @@ -218,27 +261,31 @@ private void HandleBossOutpostDeath(Npc npc, Unit killer, EventListenerService c .WithPoints(StabilityPoints) .AddParticipants(participants) .WithWinnerCorp(zone.ToPlayerOrGetOwnerPlayer(killer).CorporationEid); - channel.PublishMessage(builder.Build()); + PublishMessage(builder.Build()); } } - private void SpawnPortal(Npc npc, Unit killer, EventListenerService channel) + private void SpawnPortal(Npc npc, Unit killer) { if (!HasRiftToSpawn) return; - channel.PublishMessage(new SpawnPortalMessage(npc.Zone.Id, npc.CurrentPosition, _riftConfig)); + PublishMessage(new SpawnPortalMessage(npc.Zone.Id, npc.CurrentPosition, _riftConfig)); } - private static void SendMessage(Unit src, EventListenerService eventChannel, string msg) + private void SendMessage(Unit src, string msg) { if (!msg.IsNullOrEmpty()) { - IEventMessage eventMessage = new NpcMessage(msg, src); - Task.Run(() => eventChannel.PublishMessage(eventMessage)); + PublishMessage(new NpcMessage(msg, src)); } } + private void PublishMessage (IEventMessage eventMessage) + { + _eventChannel.PublishMessage(eventMessage); + } + public override bool Equals(object obj) { return Equals(obj as NpcBossInfo); diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 278d6e05e..c82c59615 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -480,11 +480,7 @@ public override void Enter() _movement.Start(npc); }); - if (npc.IsBoss()) - { - npc.BossInfo.OnDeAggro(); - } - + npc.BossInfo?.OnDeAggro(); base.Enter(); } @@ -812,11 +808,6 @@ public Npc(TagHelper tagHelper, EventListenerService eventChannel) public NpcSpecialType SpecialType { get; set; } public NpcBossInfo BossInfo { get; set; } - public bool IsBoss() - { - return SpecialType == NpcSpecialType.Boss && BossInfo != null; - } - [CanBeNull] private INpcGroup _group; @@ -875,9 +866,9 @@ public Player GetTagger() public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) { - if (IsBoss() && hostile.IsPlayer()) + if (hostile.IsPlayer()) { - BossInfo.OnAggro(hostile as Player, _eventChannel); + BossInfo?.OnAggro(hostile as Player); } _threatManager.GetOrAddHostile(hostile).AddThreat(threat); @@ -980,11 +971,7 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) if (player == null) return; - if (IsBoss()) - { - BossInfo.OnDamageTaken(this, player, _eventChannel); - } - + BossInfo?.OnDamageTaken(this, player); AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); } @@ -994,10 +981,7 @@ protected override void OnDead(Unit killer) var tagger = GetTagger(); Debug.Assert(zone != null, "zone != null"); - if (IsBoss()) - { - BossInfo.OnDeath(this, killer, _eventChannel); - } + BossInfo?.OnDeath(this, killer); HandleNpcDeadAsync(zone, killer, tagger).ContinueWith((t) => base.OnDead(killer)).LogExceptions(); } @@ -1013,7 +997,7 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) using (var scope = Db.CreateTransaction()) { - if (IsBoss() && BossInfo.IsLootSplit) + if (BossInfo?.IsLootSplit ?? false) { //Boss - Split loot equally to all participants List participants = new List(); From 5115cde791426ec5718fb554225d308748623eeb Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 26 May 2021 19:55:33 -0400 Subject: [PATCH 275/457] fix red npcs not properly adding threats (#325) --- src/Perpetuum/Zones/NpcSystem/Npc.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index c82c59615..85ca0e229 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -1221,7 +1221,6 @@ internal override bool IsHostile(AreaBomb bomb) /// /// 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 aggressive and within aggrorange /// - Or is the npc non-passive and the Threat is of some defined type /// /// Unit target @@ -1232,16 +1231,10 @@ public bool CanAddThreatTo(Unit target, Threat threat) if (_threatManager.Contains(target)) return true; - switch (Behavior.Type) - { - case NpcBehaviorType.Passive: - return false; - case NpcBehaviorType.Neutral: - { - return threat.type != ThreatType.Undefined; - } - } - return IsInAggroRange(target); + if (Behavior.Type == NpcBehaviorType.Passive) + return false; + + return threat.type != ThreatType.Undefined; } private void AddBodyPullThreat(Unit enemy) From 819aa063d1fe0d27b0af10d07458e765e50cf8c1 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 26 May 2021 20:02:09 -0400 Subject: [PATCH 276/457] market noshow fix (#333) --- src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index beb1699cd..7e2c3e35c 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs @@ -142,7 +142,8 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) _pbsReinforceHandler.Init(); _pbsTerritorialVisibilityHelper.Init(); - ClearChildren(); //ez azert kell, hogy a zonan ne legyenek gyerekei semmikepp + //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); } From cad3f3e9ef31649ca256d368976ee3421dd4f20c Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 28 May 2021 11:54:32 -0400 Subject: [PATCH 277/457] Gamma Market Visibility (#334) * market noshow fix * fix-feature: show orders in remote markets if character has visibility on dockingbase --- .../Markets/MarketItemsInRange.cs | 4 +- src/Perpetuum/Services/MarketEngine/Market.cs | 7 ++- .../Services/MarketEngine/MarketHandler.cs | 15 ++++- .../MarketEngine/MarketOrderRepository.cs | 4 +- .../MarketEngine/MarketPriceCollector.cs | 6 ++ .../Units/DockingBases/DockingBase.cs | 5 ++ src/Perpetuum/Zones/Gates/Gate.cs | 10 ++++ .../Zones/PBS/DockingBases/PBSDockingBase.cs | 20 +++++++ src/Perpetuum/Zones/ZoneExtensions.Unit.cs | 57 ++----------------- 9 files changed, 70 insertions(+), 58 deletions(-) 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/Services/MarketEngine/Market.cs b/src/Perpetuum/Services/MarketEngine/Market.cs index 56a060a34..75faf6b41 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() 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 f70962737..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); @@ -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(); 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/Units/DockingBases/DockingBase.cs b/src/Perpetuum/Units/DockingBases/DockingBase.cs index 4baeb7971..7522d3012 100644 --- a/src/Perpetuum/Units/DockingBases/DockingBase.cs +++ b/src/Perpetuum/Units/DockingBases/DockingBase.cs @@ -271,6 +271,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/Zones/Gates/Gate.cs b/src/Perpetuum/Zones/Gates/Gate.cs index c341d982f..5e0360de0 100644 --- a/src/Perpetuum/Zones/Gates/Gate.cs +++ b/src/Perpetuum/Zones/Gates/Gate.cs @@ -256,5 +256,15 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) 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/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index 7e2c3e35c..2c061af09 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs @@ -456,6 +456,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() { diff --git a/src/Perpetuum/Zones/ZoneExtensions.Unit.cs b/src/Perpetuum/Zones/ZoneExtensions.Unit.cs index 650323d61..03698e0e6 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Unit.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Unit.cs @@ -41,70 +41,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() .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()); From 65e25c31774b8fabb49667dcc2c4f4f82949a94d Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 28 May 2021 11:54:43 -0400 Subject: [PATCH 278/457] teleporting add/remove from zone resets the PMCQ (#335) --- src/Perpetuum/Zones/PBS/PBSHelper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 70272ed9f..042550947 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -255,8 +255,7 @@ private static void PushPlayersFromPosition(IZone zone, Position position, int r 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); } } From 211aea43cf93ab3b9f9c612c7c907e7931fadb34 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 13 Jun 2021 16:25:33 -0400 Subject: [PATCH 279/457] Boss announce fix (#340) --- .../EventProcessors/NpcStateAnnouncer.cs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs index a411624ce..f9dbf56dc 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -121,14 +121,19 @@ private bool IsUpdatable(NpcStateMessage current, NpcStateMessage next) return next.TimeStamp > current.TimeStamp; } - private void UpdateState(NpcStateMessage msg) + private bool UpdateState(NpcStateMessage msg) { var defKey = GetNpcDef(msg); - if (defKey < 0) return; - if (!_state.ContainsKey(defKey) || IsUpdatable(_state[defKey], 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; @@ -136,17 +141,20 @@ public override void HandleMessage(IEventMessage value) { if (value is NpcStateMessage msg) { - UpdateState(msg); - var announcement = BuildChatAnnouncement(msg); - var motd = BuildTopicFromState(); - if (!announcement.IsNullOrEmpty()) + if (UpdateState(msg)) { - _channelManager.Announcement(CHANNEL, _announcer, announcement); - } - if (!motd.IsNullOrEmpty()) - { - _channelManager.SetTopic(CHANNEL, _announcer, motd); + var announcement = BuildChatAnnouncement(msg); + var motd = BuildTopicFromState(); + if (!announcement.IsNullOrEmpty()) + { + _channelManager.Announcement(CHANNEL, _announcer, announcement); + } + if (!motd.IsNullOrEmpty()) + { + _channelManager.SetTopic(CHANNEL, _announcer, motd); + } } + } } } From 5f0d39f3943ea9f4993369fa9c704dfe8550e9bb Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 13 Jun 2021 16:25:55 -0400 Subject: [PATCH 280/457] Fix portalspawn (#341) * fixes to portal spawning, find walkable pos, and despawn effect bug despawning unit before effect is fully applied * refactor of despawnhelpers, track effect in pending state to avoid premature update loop cancellation --- src/Perpetuum/Players/Player.cs | 4 +- .../EventMessages/SpawnPortalMessage.cs | 5 ++ .../EventProcessors/PortalSpawner.cs | 36 ++++++++- .../Services/RiftSystem/CustomRiftConfig.cs | 7 +- .../StrongholdPlayerStateManager.cs | 64 ---------------- src/Perpetuum/Units/UnitDespawnHelper.cs | 76 ++++++++++++++++--- src/Perpetuum/Zones/Effects/EffectHandler.cs | 5 ++ 7 files changed, 116 insertions(+), 81 deletions(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index cf3f01723..cb6dbf829 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -138,7 +138,7 @@ public sealed class Player : Robot,IBlobableUnit,IBlobEmitter private readonly PlayerMovement _movement; private CombatLogger _combatLogger; private PlayerMoveCheckQueue _check; - private StrongholdPlayerDespawnHelper _despawnHelper; + private CancellableDespawnHelper _despawnHelper; public Player(IExtensionReader extensionReader, ICorporationManager corporationManager, @@ -329,7 +329,7 @@ public void SetStrongholdDespawn(TimeSpan time, UnitDespawnStrategy strategy) { if (_despawnHelper == null) { - _despawnHelper = StrongholdPlayerDespawnHelper.Create(this, time); + _despawnHelper = CancellableDespawnHelper.Create(this, time); _despawnHelper.DespawnStrategy = strategy; } } diff --git a/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs index 94c98d21b..3a6cf3362 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/SpawnPortalMessage.cs @@ -15,5 +15,10 @@ public SpawnPortalMessage(int sourceZone, Position srcPosition, CustomRiftConfig SourceZone = sourceZone; RiftConfig = riftConfig; } + + public override string ToString() + { + return $"SpawnPortalMessage:z:{SourceZone};Pos:{SourcePosition};{RiftConfig};"; + } } } diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs index 7a14d797d..ca5338ff6 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/PortalSpawner.cs @@ -1,8 +1,10 @@ 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 { @@ -22,10 +24,29 @@ public PortalSpawner(IEntityServices entityServices, IZoneManager zoneManager) private bool ValidateMessage(SpawnPortalMessage msg) { if (msg.RiftConfig == null || !_zoneManager.ContainsZone(msg.SourceZone)) + { return false; - if (!_zoneManager.GetZone(msg.SourceZone).IsWalkable(msg.SourcePosition)) + } + else if (msg.SourcePosition == Position.Empty) + { return false; - return true; + } + 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) @@ -33,10 +54,19 @@ 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, msg.SourcePosition, () => + CustomRiftSpawner.TrySpawnRift(msg.RiftConfig, _zoneManager, msg.SourceZone, spawnPos, () => { return (StrongholdEntryRift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.TARGETTED_RIFT); }); diff --git a/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs index 4874ae490..c87206e72 100644 --- a/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs +++ b/src/Perpetuum/Services/RiftSystem/CustomRiftConfig.cs @@ -108,9 +108,9 @@ public static bool TrySpawnRift(CustomRiftConfig config, IZoneManager zoneManage var targetPos = targetDestination.GetPosition(zoneTarget); var rift = riftFactory(); - rift.AddToZone(zone, sourcePosition, ZoneEnterType.NpcSpawn); 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; @@ -149,6 +149,11 @@ public Destination GetDestination() { return _destinations.GetRandom(); } + + public override string ToString() + { + return $"CustomRiftConfig:id:{Id};n:{Name};l:{Lifespan};u:{MaxUses};"; + } } diff --git a/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs index f0d5e3af3..179bb3f46 100644 --- a/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs +++ b/src/Perpetuum/Services/Strongholds/StrongholdPlayerStateManager.cs @@ -1,9 +1,7 @@ using Perpetuum.Data; -using Perpetuum.ExportedTypes; using Perpetuum.Players; using Perpetuum.Services.EventServices; using Perpetuum.Services.EventServices.EventMessages; -using Perpetuum.Units; using Perpetuum.Zones; using System; @@ -121,66 +119,4 @@ private void SendMessage(Player player, string message) _eventChannel.PublishMessage(new DirectMessage(player.Character, message)); } } - - public class StrongholdPlayerDespawnHelper : UnitDespawnHelper - { - private static readonly EffectType DespawnEffect = EffectType.effect_stronghold_despawn_timer; - - private StrongholdPlayerDespawnHelper(TimeSpan despawnTime) : base(despawnTime) { } - - private bool _canceled = false; - public void Cancel(Unit unit) - { - _canceled = true; - RemoveDespawnEffect(unit); - } - - public bool HasEffect(Unit unit) - { - return unit.EffectHandler.ContainsToken(_effectToken); - } - - private bool _detectedEffectApplied = false; - private bool EffectLive(Unit unit) - { - var effectRunning = HasEffect(unit); - if (!_detectedEffectApplied) - { - _detectedEffectApplied = effectRunning; - } - return _detectedEffectApplied == effectRunning; - } - - public override void Update(TimeSpan time, Unit unit) - { - _timer.Update(time).IsPassed(() => - { - if (_canceled || EffectLive(unit)) - return; - - DespawnStrategy?.Invoke(unit); - }); - } - - private void RemoveDespawnEffect(Unit unit) - { - unit.EffectHandler.RemoveEffectByToken(_effectToken); - } - - private void ApplyDespawnEffect(Unit unit) - { - var effectBuilder = unit.NewEffectBuilder().SetType(DespawnEffect).WithDuration(_despawnTime).WithToken(_effectToken); - unit.ApplyEffect(effectBuilder); - } - - public new static StrongholdPlayerDespawnHelper Create(Unit unit, TimeSpan despawnTime) - { - var helper = new StrongholdPlayerDespawnHelper(despawnTime); - helper.ApplyDespawnEffect(unit); - return helper; - } - } } - - - diff --git a/src/Perpetuum/Units/UnitDespawnHelper.cs b/src/Perpetuum/Units/UnitDespawnHelper.cs index 20384baa7..b0656902f 100644 --- a/src/Perpetuum/Units/UnitDespawnHelper.cs +++ b/src/Perpetuum/Units/UnitDespawnHelper.cs @@ -5,12 +5,29 @@ 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 { + public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { protected get; set; } + public UnitDespawnStrategy DespawnStrategy { protected get; set; } + + public virtual EffectType DespawnEffect + { + get { return 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) @@ -18,17 +35,18 @@ protected UnitDespawnHelper(TimeSpan despawnTime) _despawnTime = despawnTime; } - public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { protected get; set; } - - public UnitDespawnStrategy DespawnStrategy { protected get; set; } + protected bool HasEffectOrPending(Unit unit) + { + return unit.EffectHandler.ContainsOrPending(_effectToken); + } - public virtual 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; @@ -44,8 +62,6 @@ public virtual void Update(TimeSpan time,Unit unit) }); } - protected readonly EffectToken _effectToken = EffectToken.NewToken(); - private void TryReApplyDespawnEffect(Unit unit) { var canApplyDespawnEffect = CanApplyDespawnEffect; @@ -59,9 +75,9 @@ private void TryReApplyDespawnEffect(Unit unit) 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); + var effectBuilder = unit.NewEffectBuilder().SetType(DespawnEffect).WithDuration(_despawnTime).WithToken(_effectToken); unit.ApplyEffect(effectBuilder); } @@ -70,12 +86,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); helper.ApplyDespawnEffect(unit); return helper; } + } + + public class CancellableDespawnHelper : UnitDespawnHelper + { + public override EffectType DespawnEffect + { + get { return 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 new static CancellableDespawnHelper Create(Unit unit, TimeSpan despawnTime) + { + var helper = new CancellableDespawnHelper(despawnTime); + helper.ApplyDespawnEffect(unit); + return helper; + } } } \ No newline at end of file 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); From e52491eb354697c65482884ce478578e6e9413f0 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 13 Jun 2021 16:26:56 -0400 Subject: [PATCH 281/457] Controllayer bit set by image + PBSTerraformZone from teleports command (#337) --- .../PerpetuumBootstrapper.cs | 2 + .../Perpetuum.RequestHandlers.csproj | 2 + .../Zone/StatsMapDrawing/ZoneDrawStatMap.cs | 27 +++++++ .../Zone/ZoneCreateTerraformLimit.cs | 66 +++++++++++++++++ .../Zone/ZoneSetLayerWithBitMap.cs | 41 +++++++++++ src/Perpetuum/Commands.cs | 22 ++++++ .../ChatCommands/AdminCommandHandlers.cs | 70 ++++++++++++++++++- 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs create mode 100644 src/Perpetuum.RequestHandlers/Zone/ZoneSetLayerWithBitMap.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 51c8664f9..76840db76 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2768,6 +2768,8 @@ private void RegisterZoneRequestHandlers() RegisterZoneRequestHandler(Commands.ZoneSetPlantsMode); RegisterZoneRequestHandler(Commands.ZoneCreateGarden); RegisterZoneRequestHandler(Commands.ZoneCreateIsland); + RegisterZoneRequestHandler(Commands.ZoneCreateTerraformLimit); + RegisterZoneRequestHandler(Commands.ZoneSetLayerWithBitMap); RegisterZoneRequestHandler(Commands.ZoneDrawBlockingByDefinition); RegisterZoneRequestHandler(Commands.ZoneCleanBlockingByDefinition); RegisterZoneRequestHandler(Commands.ZoneCleanObstacleBlocking); diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index dbd1d4293..d31aa3a4e 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -580,6 +580,7 @@ + @@ -619,6 +620,7 @@ + diff --git a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs index 2e529ea3d..0247d3744 100644 --- a/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs +++ b/src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs @@ -56,6 +56,18 @@ public ZoneDrawStatMap(IFileSystem fileSystem,SaveBitmapHelper saveBitmapHelper, 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 ); @@ -632,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) => diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs new file mode 100644 index 000000000..5450ab154 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs @@ -0,0 +1,66 @@ +using Perpetuum.Host.Requests; +using Perpetuum.Zones; +using Perpetuum.Zones.Terrains; +using System.Drawing; + +namespace Perpetuum.RequestHandlers.Zone +{ + public class ZoneCreateTerraformLimit : IRequestHandler + { + private void Clear(IZoneRequest request) + { + request.Zone.Terrain.Controls.UpdateAll((x, y, c) => + { + c.TerraformProtected = false; + return c; + }); + } + + private void SetRadiusOnTeleports(IZoneRequest request, int radius) + { + var zone = request.Zone; + var teleports = request.Zone.GetTeleportColumns(); + foreach (var tele in teleports) + { + var center = tele.CurrentPosition.ToPoint(); + var x0 = (center.X - radius).Clamp(0, zone.Terrain.Controls.Width); + var x1 = (center.X + radius).Clamp(0, zone.Terrain.Controls.Width); + var y0 = (center.Y - radius).Clamp(0, zone.Terrain.Controls.Height); + var y1 = (center.Y + radius).Clamp(0, zone.Terrain.Controls.Height); + for (var y = y0; y < y1; y++) + { + for (var x = x0; x < x1; x++) + { + var p = new Point(x, y); + if (center.Distance(p) < radius) + { + zone.Terrain.Controls.UpdateValue(x, y, (c) => + { + c.TerraformProtected = true; + return 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/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/Commands.cs b/src/Perpetuum/Commands.cs index 2a6738b0e..58b35e26e 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -1222,6 +1222,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", diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 84a669c5d..737fd7bf4 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -372,7 +372,9 @@ public static void ZoneDrawStatMap(AdminCommandData data) CheckZoneId(data, zoneId); var 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) @@ -684,11 +686,77 @@ public static void ZoneCreateIsland(AdminCommandData data) Dictionary dictionary = new Dictionary() { - { "low", lvl } + { k.low, lvl } }; string cmd = string.Format("zoneCreateIsland:zone_{0}:{1}", data.Sender.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; + + int radius = 0; + string mode; + try + { + mode = data.Command.Args[0]; + if(mode!="clear") + radius = 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.distance, radius }, + { k.mode, mode } + }; + + string cmd = string.Format("ZoneCreateTerraformLimit:zone_{0}:{1}", data.Sender.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; + + string fileName; + int flagValue; + try + { + var flagName = data.Command.Args[0]; + fileName = data.Command.Args[1]; + flagValue = (int)EnumHelper.GetEnumFromName(flagName); + } + catch (Exception ex) + { + if (ex is FormatException || ex is ArgumentNullException) + throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + throw; + } + + Dictionary dictionary = new Dictionary() + { + { k.file, fileName }, + { k.flags, flagValue } + }; + + string cmd = string.Format("ZoneSetLayerWithBitMap:zone_{0}:{1}", data.Sender.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) From 7888e320f25a64e9de8fa160b17fedb8527d887f Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 17 Jun 2021 20:37:40 -0400 Subject: [PATCH 282/457] Npc optimizations: pre-compute EP (#346) * compute EP not in massive ondead transaction * clean imports * cache the db fetched value lazily as npc definitions are first encountered * unused import --- src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Zones/NpcSystem/Npc.cs | 41 +++++++++++++++----------- src/Perpetuum/Zones/NpcSystem/NpcEp.cs | 19 ++++++++++++ 3 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/NpcEp.cs diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index c77693b54..69b342a6c 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -714,6 +714,7 @@ + diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 85ca0e229..b561cf811 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -15,7 +15,6 @@ using Perpetuum.Modules.Weapons; using Perpetuum.PathFinders; using Perpetuum.Players; -using Perpetuum.Services.EventServices; using Perpetuum.Services.Looting; using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; @@ -790,14 +789,12 @@ public class Npc : Creature, ITaggable private Lazy _maxCombatRange; private Lazy _optimalCombatRange; private TimeSpan _lastHelpCalled; - private readonly EventListenerService _eventChannel; private readonly IPseudoThreatManager _pseudoThreatManager; - public Npc(TagHelper tagHelper, EventListenerService eventChannel) + public Npc(TagHelper tagHelper) { _maxCombatRange = new Lazy(CalculateMaxCombatRange); _optimalCombatRange = new Lazy(CalculateCombatRange); - _eventChannel = eventChannel; _tagHelper = tagHelper; _threatManager = new ThreatManager(); AI = new StackFSM(); @@ -807,6 +804,7 @@ public Npc(TagHelper tagHelper, EventListenerService eventChannel) public NpcBehavior Behavior { get; set; } public NpcSpecialType SpecialType { get; set; } public NpcBossInfo BossInfo { get; set; } + public int EP { get; private set; } [CanBeNull] private INpcGroup _group; @@ -1035,27 +1033,18 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) 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) + if (EP > 0) { var awardedPlayers = new List(); foreach (var hostile in ThreatManager.Hostiles) { var playerUnit = hostile.unit; var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); - hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, ep); + hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, EP); awardedPlayers.Add(playerUnit); } - _pseudoThreatManager.AwardPseudoThreats(awardedPlayers, zone, ep); + _pseudoThreatManager.AwardPseudoThreats(awardedPlayers, zone, EP); } scope.Complete(); @@ -1147,6 +1136,7 @@ protected override void OnUpdate(TimeSpan time) protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { + SetEP(zone); States.Aggressive = Behavior.Type == NpcBehaviorType.Aggressive; base.OnEnterZone(zone, enterType); @@ -1161,6 +1151,23 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) } } + private void SetEP(IZone zone) + { + if (zone.Configuration.Type == ZoneType.Training) + { + EP = 0; + return; + } + + var ep = NpcEp.GetEpForNpc(this); + + if (zone.Configuration.IsBeta) + ep *= 2; + + EP = ep; + Logger.DebugInfo($"Ep4Npc:{ep} def:{Definition} {ED.Name}"); + } + public override string InfoString { get @@ -1184,7 +1191,7 @@ public override string InfoString private bool IsInAggroRange(Unit target) { - return this.IsStationary || IsInRangeOf3D(target, AGGRO_RANGE); + return IsStationary || IsInRangeOf3D(target, AGGRO_RANGE); } protected override void OnUnitLockStateChanged(Lock @lock) diff --git a/src/Perpetuum/Zones/NpcSystem/NpcEp.cs b/src/Perpetuum/Zones/NpcSystem/NpcEp.cs new file mode 100644 index 000000000..6a7634339 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/NpcEp.cs @@ -0,0 +1,19 @@ +using Perpetuum.Data; +using System.Collections.Generic; + +namespace Perpetuum.Zones.NpcSystem +{ + public static class NpcEp + { + private static readonly IDictionary _npcEp = new Dictionary(); + + public static int GetEpForNpc(Npc npc) + { + var def = npc.Definition; + if (def <= 0) + return 0; + + return _npcEp.GetOrAdd(def, () =>Db.Query().CommandText("GetNpcKillEp").SetParameter("@definition", def).ExecuteScalar()); + } + } +} From 12e8edb94d1ad9f5ef3fad2e732314c1b6dfb073 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 17 Jun 2021 20:41:30 -0400 Subject: [PATCH 283/457] Robot module/components IEnum->array (#345) * use lazy init array to avoid runtime cost of Ienumerable loop * this appears to be safe.. more testing desired * also only called before an .Initialize() call on the component or parent robot --- src/Perpetuum/Robots/Robot.cs | 35 +++++++++++++++++++++----- src/Perpetuum/Robots/RobotComponent.cs | 18 +++++++++++-- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 6b839eb14..09d626051 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -68,6 +68,29 @@ protected Robot() public RobotTemplate Template { get; set; } + public override void Initialize() + { + InitComponents(); + InitModules(); + base.Initialize(); + } + + private Lazy> _modules; + private Lazy> _activeModules; + private void InitModules() + { + _modules = new Lazy>(() => RobotComponents.SelectMany(c => c.Modules).ToArray()); + _activeModules = new Lazy>(() => Modules.OfType().ToArray()); + } + + private Lazy> _components; + private Lazy> _robotComponents; + private void InitComponents() + { + _components = new Lazy>(() => Children.OfType().ToArray()); + _robotComponents = new Lazy>(() => Components.OfType().ToArray()); + } + protected virtual void OnLockError(Lock @lock, ErrorCodes error) { } @@ -316,24 +339,24 @@ public RobotComponent GetRobotComponent(RobotComponentType componentType) return RobotComponents.FirstOrDefault(c => c.Type == componentType); } - public IEnumerable ActiveModules + public IEnumerable Modules { - get { return Modules.OfType(); } + get { return _modules.Value; } } - public IEnumerable Modules + public IEnumerable ActiveModules { - get { return RobotComponents.SelectMany(c => c.Modules); } + get { return _activeModules.Value; } } public IEnumerable Components { - get { return Children.OfType(); } + get { return _components.Value; } } public IEnumerable RobotComponents { - get { return Components.OfType(); } + get { return _robotComponents.Value; } } public void CheckEnergySystemAndThrowIfFailed() diff --git a/src/Perpetuum/Robots/RobotComponent.cs b/src/Perpetuum/Robots/RobotComponent.cs index 7b4ac8f7a..6665afdae 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>(() => Children.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) From 35ce2505c6f0b1e632962b5264a562117696a90b Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 19 Jun 2021 15:14:54 -0400 Subject: [PATCH 284/457] More Robot.Init() (#353) --- src/Perpetuum/Robots/Robot.cs | 9 ++------- src/Perpetuum/Robots/RobotComponent.cs | 2 +- src/Perpetuum/Robots/RobotHelper.cs | 8 ++++++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 09d626051..2a603ce52 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -71,24 +71,19 @@ protected Robot() public override void Initialize() { InitComponents(); - InitModules(); base.Initialize(); } private Lazy> _modules; private Lazy> _activeModules; - private void InitModules() - { - _modules = new Lazy>(() => RobotComponents.SelectMany(c => c.Modules).ToArray()); - _activeModules = new Lazy>(() => Modules.OfType().ToArray()); - } - private Lazy> _components; private Lazy> _robotComponents; 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()); } protected virtual void OnLockError(Lock @lock, ErrorCodes error) diff --git a/src/Perpetuum/Robots/RobotComponent.cs b/src/Perpetuum/Robots/RobotComponent.cs index 6665afdae..b68ba5480 100644 --- a/src/Perpetuum/Robots/RobotComponent.cs +++ b/src/Perpetuum/Robots/RobotComponent.cs @@ -53,7 +53,7 @@ public override void Initialize() private void InitModules() { _modules = new Lazy>(() => Children.OfType().ToArray()); - _activeModules = new Lazy>(() => Children.OfType().ToArray()); + _activeModules = new Lazy>(() => Modules.OfType().ToArray()); } public override void AcceptVisitor(IEntityVisitor visitor) 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) From 9cd908d4a8359e43155978f1810e02630907a2d3 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 19 Jun 2021 15:30:14 -0400 Subject: [PATCH 285/457] Npc EP lookup concurrency fix (#354) --- src/Perpetuum/Zones/NpcSystem/NpcEp.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/NpcEp.cs b/src/Perpetuum/Zones/NpcSystem/NpcEp.cs index 6a7634339..edcdadfec 100644 --- a/src/Perpetuum/Zones/NpcSystem/NpcEp.cs +++ b/src/Perpetuum/Zones/NpcSystem/NpcEp.cs @@ -1,19 +1,19 @@ using Perpetuum.Data; -using System.Collections.Generic; +using System.Collections.Concurrent; namespace Perpetuum.Zones.NpcSystem { public static class NpcEp { - private static readonly IDictionary _npcEp = new Dictionary(); + private static readonly ConcurrentDictionary _npcEp = new ConcurrentDictionary(); public static int GetEpForNpc(Npc npc) { - var def = npc.Definition; - if (def <= 0) + var definition = npc.Definition; + if (definition <= 0) return 0; - return _npcEp.GetOrAdd(def, () =>Db.Query().CommandText("GetNpcKillEp").SetParameter("@definition", def).ExecuteScalar()); + return _npcEp.GetOrAdd(definition, (d) => Db.Query().CommandText("GetNpcKillEp").SetParameter("@definition", d).ExecuteScalar()); } } } From 4d7a1d9fe269b7c0a8a0617eedae11aea7f27b74 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 19 Jun 2021 15:31:27 -0400 Subject: [PATCH 286/457] Fix npc roamspawn (#352) * wip, todo notes * handle roam respawning and flock respawning gracefully * nothing to catch here * be more polymorphic n such * remove unused stuff --- .../PerpetuumBootstrapper.cs | 6 +-- src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 21 +++++----- .../Zones/NpcSystem/Flocks/NormalFlock.cs | 4 -- .../NpcSystem/Flocks/RemoteSpawningFlock.cs | 2 +- .../Zones/NpcSystem/Flocks/RoamingFlock.cs | 42 +++++++++++++++++++ .../PathFinders/FreeRoamingPathFinder.cs | 2 +- .../Presences/PathFinders/RoamingStates.cs | 7 ++-- 8 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 76840db76..8db3048a3 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1501,10 +1501,10 @@ public void RegisterNpcs() RegisterFlock(PresenceType.Dynamic); RegisterFlock(PresenceType.DynamicExtended); RegisterFlock(PresenceType.Random); - RegisterFlock(PresenceType.Roaming); - RegisterFlock(PresenceType.FreeRoaming); + RegisterFlock(PresenceType.Roaming); + RegisterFlock(PresenceType.FreeRoaming); RegisterFlock(PresenceType.Interzone); - RegisterFlock(PresenceType.InterzoneRoaming); + RegisterFlock(PresenceType.InterzoneRoaming); RegisterPresence(PresenceType.Normal); RegisterPresence(PresenceType.Direct).OnActivated(e => diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 69b342a6c..62d43a9e4 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -713,6 +713,7 @@ + diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index 8321725ef..c51f37d0e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -117,6 +117,15 @@ public void SpawnAllMembers() 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; @@ -128,17 +137,9 @@ protected virtual void CreateMemberInZone() npc.LootGenerator = gen; npc.HomeRange = HomeRange; - npc.HomePosition = SpawnOrigin; + npc.HomePosition = 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); @@ -222,7 +223,7 @@ private Position SpawnOriginSelector(Presence presence) { return rp.SpawnOriginForRandomPresence; } - case RoamingPresence roaming: + case IRoamingPresence roaming: { return roaming.SpawnOrigin; } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/NormalFlock.cs index a3899894f..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; @@ -80,8 +78,6 @@ private TimeSpan GetRespawnTime() public override void Update(TimeSpan time) { - _elapsedTime += time; - if (!Presence.Configuration.IsRespawnAllowed) return; diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs index faa641d39..4aeeab36c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs @@ -15,7 +15,7 @@ protected override Position GetSpawnPosition(Position spawnOrigin) { if (Presence is DynamicPresenceExtended presence) { - return presence.SpawnLocation; + return presence.SpawnLocation.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..d5a7e8b56 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs @@ -0,0 +1,42 @@ +using Perpetuum.Zones.NpcSystem.Presences; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +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); + } + + private 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) + { + return roaming.SpawnOrigin.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs index c05fa6731..5e156f4ca 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs @@ -24,7 +24,7 @@ public FreeRoamingPathFinder(IZone zone) public Point FindSpawnPosition(IRoamingPresence presence) { - var homeRange = presence.Flocks.Max(f => f.HomeRange); + var homeRange = presence.Flocks.Max(f => f.HomeRange).Clamp(10, 40); var rangeMax = homeRange * 2; var walkableArea = _zone.FindWalkableArea(presence.Area, rangeMax * rangeMax); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs index c3397360b..77a8bd83f 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -69,13 +69,14 @@ private void SpawnFlocks() { Position spawnPosition; bool anyPlayersAround; + int range = 200; do { spawnPosition = _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); - anyPlayersAround = _presence.Zone.Players.WithinRange(spawnPosition, 200).Any(); - - } while (anyPlayersAround); + anyPlayersAround = _presence.Zone.Players.WithinRange(spawnPosition, range).Any(); + range--; + } while (anyPlayersAround && range > 0); _presence.SpawnOrigin = spawnPosition; _presence.CurrentRoamingPosition = spawnPosition; From a0744a8d32664b68ecf03f5df7c27a42a9b4dcc8 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 19 Jun 2021 15:32:08 -0400 Subject: [PATCH 287/457] prevent combination of seeded cts (#349) --- src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs index c39ad1c94..79754aab9 100644 --- a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs +++ b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs @@ -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); From a8a2e050ced363010360d97872e3aaa044aa7e86 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 4 Aug 2021 22:01:22 -0400 Subject: [PATCH 288/457] Gamma tech limit (#348) --- src/Perpetuum/ErrorCodes.cs | 3 ++- src/Perpetuum/Zones/PBS/PBSEgg.cs | 4 ++-- src/Perpetuum/Zones/PBS/PBSHelper.cs | 28 +++++++++++++----------- src/Perpetuum/Zones/ZoneConfiguration.cs | 4 +++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index 5f414d665..b4ade28c7 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -702,6 +702,7 @@ public enum ErrorCodes InviteOnlyServer, SteamLoginDisabled, AccountAlreadyExists, - RobotWrongType + RobotWrongType, + PBSTechLevelTooHighForZone } } diff --git a/src/Perpetuum/Zones/PBS/PBSEgg.cs b/src/Perpetuum/Zones/PBS/PBSEgg.cs index b8734a7f3..c222ac06f 100644 --- a/src/Perpetuum/Zones/PBS/PBSEgg.cs +++ b/src/Perpetuum/Zones/PBS/PBSEgg.cs @@ -153,7 +153,8 @@ public void CheckDefinitionRelatedConditionsOrThrow(IZone zone, Position spawnPo throw new PerpetuumException(ErrorCodes.ConsistencyError); } - + (pbsNodeEntityDefault.Tier.level > zone.Configuration.PBSTechLimit).ThrowIfTrue(ErrorCodes.PBSTechLevelTooHighForZone); + if (pbsNodeEntityDefault.CategoryFlags.IsCategory(CategoryFlags.cf_pbs_docking_base)) { //check deploying of bases @@ -170,7 +171,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 042550947..5fc160345 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -113,16 +113,8 @@ public static int GetCapsuleDefinitionByPBSObjectDefinition(int nodeDefinition) } public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position position, long owner, - EntityDefault dockingbasEntityDefault) + EntityDefault dockingbaseEntityDefault) { - //only a set number of bases for one corporation - var baseCountPerCorporation = zone.Units.Count(u => u is PBSDockingBase && u.Owner == owner); - - if (baseCountPerCorporation + 1 > MAX_BASES_PER_CORP_PER_ZONE) - { - return ErrorCodes.MaxDockingBasePerZonePerCorporationReached; - } - //current total number of bases per zone var baseCountPerZone = zone.Units.Count(u => u is PBSDockingBase); @@ -134,16 +126,26 @@ public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position po return ErrorCodes.MaxDockingBasePerZoneReached; } - var typeExclusiveRange = dockingbasEntityDefault.Config.typeExclusiveRange; + if (maxBasesPerZone > MAX_BASES_PER_CORP_PER_ZONE) + { + //only a set number of bases for one corporation + var baseCountPerCorporation = zone.Units.Count(u => u is PBSDockingBase && u.Owner == owner); + + if (baseCountPerCorporation + 1 > MAX_BASES_PER_CORP_PER_ZONE) + { + return ErrorCodes.MaxDockingBasePerZonePerCorporationReached; + } + } + + var typeExclusiveRange = dockingbaseEntityDefault.Config.typeExclusiveRange; if (typeExclusiveRange == null) { - Logger.Error("no typeExclusiveRange defined for " + dockingbasEntityDefault); + Logger.Error("no typeExclusiveRange defined for " + dockingbaseEntityDefault); 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; } diff --git a/src/Perpetuum/Zones/ZoneConfiguration.cs b/src/Perpetuum/Zones/ZoneConfiguration.cs index 2a4155e31..f99d61f95 100644 --- a/src/Perpetuum/Zones/ZoneConfiguration.cs +++ b/src/Perpetuum/Zones/ZoneConfiguration.cs @@ -63,7 +63,8 @@ public IEnumerable GetAll() PlantAltitudeScale = record.GetValue("plantaltitudescale"), Note = record.GetValue("note"), - TimeLimitMinutes = record.GetValue("timeLimitMinutes") + TimeLimitMinutes = record.GetValue("timeLimitMinutes"), + PBSTechLimit = record.GetValue("pbsTechLimit") ?? 0 }; config.PlantRules = _plantRuleLoader.LoadPlantRulesWithOverrides(config.plantRuleSetId); @@ -107,6 +108,7 @@ public sealed class ZoneConfiguration public int ListenerPort { get; set; } public int? TimeLimitMinutes { get; set; } + public int PBSTechLimit { get; set; } public ZoneType Type { get; set; } From 6de288cfc19d5d4b333c2e944e913f8c817df33a Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 4 Aug 2021 22:35:53 -0400 Subject: [PATCH 289/457] Random-Static-Expiring-Respawning Presence (#355) * wip, some kind of presence that spawns in random location away from key entities, expires, but also respawns eventually * some rename, refactor, cleanup * proximity check with like-presence spawns --- .../PerpetuumBootstrapper.cs | 14 +-- src/Perpetuum/Perpetuum.csproj | 2 + .../Zones/NpcSystem/Flocks/RoamingFlock.cs | 5 +- src/Perpetuum/Zones/NpcSystem/Npc.cs | 8 +- .../NpcSystem/Presences/DynamicPresence.cs | 43 +++++--- .../RandomSpawningExpiringPresence.cs | 103 ++++++++++++++++++ .../StaticExpiringFlock.cs | 27 +++++ .../InterzonePresences/InterzonePresence.cs | 5 + .../InterzonePresenceConfigReader.cs | 2 +- .../Presences/PathFinders/RoamingStates.cs | 100 ++++++++++++----- .../Zones/NpcSystem/Presences/Presence.cs | 2 + .../Zones/NpcSystem/Presences/PresenceType.cs | 3 +- .../NpcSystem/Presences/RoamingPresence.cs | 3 + .../Zones/NpcSystem/ThreatManager.cs | 6 + src/Perpetuum/Zones/ZoneExtensions.cs | 1 + 15 files changed, 263 insertions(+), 61 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 8db3048a3..904387b52 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -183,6 +183,7 @@ using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; using Perpetuum.Services.Strongholds; +using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; namespace Perpetuum.Bootstrapper { @@ -1500,6 +1501,7 @@ public void RegisterNpcs() RegisterFlock(PresenceType.DynamicPool); RegisterFlock(PresenceType.Dynamic); RegisterFlock(PresenceType.DynamicExtended); + RegisterFlock(PresenceType.ExpiringRandom); RegisterFlock(PresenceType.Random); RegisterFlock(PresenceType.Roaming); RegisterFlock(PresenceType.FreeRoaming); @@ -1514,6 +1516,7 @@ public void RegisterNpcs() RegisterPresence(PresenceType.DynamicPool); RegisterPresence(PresenceType.Dynamic); RegisterPresence(PresenceType.DynamicExtended); + RegisterPresence(PresenceType.ExpiringRandom); RegisterPresence(PresenceType.Random); RegisterPresence(PresenceType.Roaming); RegisterPresence(PresenceType.FreeRoaming); @@ -1535,20 +1538,11 @@ public void RegisterNpcs() switch (p.Configuration.PresenceType) { case PresenceType.Roaming: - { roamingPresence.PathFinder = new NormalRoamingPathFinder(zone); break; - } - case PresenceType.FreeRoaming: - { - roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); - break; - } - case PresenceType.InterzoneRoaming: - { + default: roamingPresence.PathFinder = new FreeRoamingPathFinder(zone); break; - } } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 62d43a9e4..afcb72df5 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -716,6 +716,8 @@ + + diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs index d5a7e8b56..93c2b73fd 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RoamingFlock.cs @@ -1,5 +1,6 @@ using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; using System; namespace Perpetuum.Zones.NpcSystem.Flocks @@ -21,7 +22,7 @@ public override void Update(TimeSpan time) base.Update(time); } - private bool IsPresenceInSpawningState() + protected virtual bool IsPresenceInSpawningState() { if (Presence is IRoamingPresence roaming) { @@ -34,7 +35,7 @@ protected override Position GetSpawnPosition(Position spawnOrigin) { if (Presence is IRoamingPresence roaming) { - return roaming.SpawnOrigin.Clamp(Presence.Zone.Size); + spawnOrigin = roaming.SpawnOrigin.Clamp(Presence.Zone.Size); } return base.GetSpawnPosition(spawnOrigin); } diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index b561cf811..f4a7c27ae 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -110,7 +110,7 @@ public override void Update(TimeSpan time) return; } - if (!npc.ThreatManager.Hostiles.IsEmpty) + if (npc.ThreatManager.IsThreatened) { ToAggressorAI(); return; @@ -133,7 +133,7 @@ public override void Enter() public override void Update(TimeSpan time) { - if (!npc.ThreatManager.Hostiles.IsEmpty) + if (npc.ThreatManager.IsThreatened) { ToAggressorAI(); } @@ -536,7 +536,7 @@ public override void Update(TimeSpan time) return; } - if (npc.ThreatManager.Hostiles.IsEmpty) + if (!npc.ThreatManager.IsThreatened) { EnterEvadeMode(); return; @@ -1363,7 +1363,7 @@ public void Visit(AreaBomb bomb) return; var threat = Threat.BODY_PULL; - if (!_npc.ThreatManager.Hostiles.IsEmpty) + if (_npc.ThreatManager.IsThreatened) { var h = _npc.ThreatManager.GetMostHatedHostile(); if (h != null) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/DynamicPresence.cs index fbbf70db1..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, IPresenceConfiguration configuration) : base(zone, configuration) + public ExpiringPresence(IZone zone, IPresenceConfiguration configuration) : base(zone, configuration) { if (Configuration.DynamicLifeTime != null) - LifeTime = TimeSpan.FromMilliseconds((int)Configuration.DynamicLifeTime); - } - - public override Area Area - { - get { return Zone.Size.ToArea(); } + 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 CombatAI); + var x = Flocks.GetMembers().Any(m => m.ThreatManager.IsThreatened); if (x) ResetDynamicDespawnTimer(); @@ -49,10 +40,30 @@ 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); } } 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..57f7b815d --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs @@ -0,0 +1,103 @@ +using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.Presences.PathFinders; +using System; +using System.Linq; +using System.Drawing; +using Perpetuum.ExportedTypes; +using Perpetuum.Units.DockingBases; +using Perpetuum.Units; +using Perpetuum.Zones.Teleporting; + +namespace Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence +{ + /// + /// A non-roaming ExpiringPresence that would spawning with Roaming rules + /// + public class RandomSpawningExpiringPresence : ExpiringPresence, IRoamingPresence + { + public StackFSM StackFSM { get; } + 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); + + StackFSM = new StackFSM(); + StackFSM.Push(new StaticSpawnState(this)); + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + StackFSM.Update(time); + } + + protected override void OnPresenceExpired() + { + ResetDynamicDespawnTimer(); + foreach (var flock in Flocks) + { + flock.RemoveAllMembersFromZone(true); + } + } + + public void OnSpawned() + { + ResetDynamicDespawnTimer(); + } + } + + public class StaticSpawnState : SpawnState + { + private readonly int BASE_RADIUS = 300; + private readonly int PLAYER_RADIUS = 150; + public StaticSpawnState(IRoamingPresence presence, int playerMinDist = 200) : base(presence, playerMinDist) { } + + protected override void OnSpawned() + { + _presence.OnSpawned(); + _presence.StackFSM.Push(new NullRoamingState(_presence)); + } + + private bool IsInRange(Position position) + { + var zone = _presence.Zone; + if (zone.Configuration.IsGamma && zone.IsUnitWithCategoryInRange(CategoryFlags.cf_pbs_docking_base, position, BASE_RADIUS)) + return true; + else if (zone.GetStaticUnits().OfType().WithinRange2D(position, BASE_RADIUS).Any()) + return true; + else if (zone.GetStaticUnits().OfType().WithinRange2D(position, PLAYER_RADIUS).Any()) + return true; + else if (zone.PresenceManager.GetPresences().OfType().Where(p => p.SpawnOrigin.IsInRangeOf2D(position, BASE_RADIUS)).Any()) + return true; + + return zone.Players.WithinRange2D(position, PLAYER_RADIUS).Any(); + } + + protected override void SpawnFlocks() + { + Position spawnPosition; + bool anyPlayersAround; + int maxRetries = 100; + + do + { + spawnPosition = _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); + anyPlayersAround = IsInRange(spawnPosition); + maxRetries--; + } while (anyPlayersAround && maxRetries > 0); + + if (anyPlayersAround) + { + _presence.Log("FAILED to resolve spawn position out of range of players: " + spawnPosition); + return; + } + + DoSpawning(spawnPosition); + } + } +} 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..f2f4a75d0 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs @@ -0,0 +1,27 @@ +using Perpetuum.Zones.NpcSystem.Flocks; + +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 RandomSpawningExpiringPresence pres) + { + return pres.StackFSM.Current is StaticSpawnState; + } + return base.IsPresenceInSpawningState(); + } + + protected override Position GetSpawnPosition(Position spawnOrigin) + { + if (Presence is RandomSpawningExpiringPresence pres) + { + spawnOrigin = pres.SpawnOrigin.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index 8947394d9..6c31508e8 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -27,6 +27,11 @@ protected override void OnUpdate(TimeSpan time) StackFSM.Update(time); base.OnUpdate(time); } + + public void OnSpawned() + { + ResetDynamicDespawnTimer(); + } } public class InterzonePresence : DynamicPresence diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs index 835a6e59d..07dced5ff 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresenceConfigReader.cs @@ -11,7 +11,7 @@ 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") + 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(); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs index 77a8bd83f..a91f89bd4 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -7,39 +7,53 @@ namespace Perpetuum.Zones.NpcSystem.Presences.PathFinders { - public class SpawnState : IState { - private readonly IRoamingPresence _presence; + protected readonly IRoamingPresence _presence; private TimeSpan _delay = TimeSpan.Zero; - private bool _spawning; - private bool _spawned; + protected bool _spawning; + protected bool _spawned; private double _repawnDelayModifier = 0.0; - public SpawnState(IRoamingPresence presence) + protected readonly int _playerMinDist; + + public SpawnState(IRoamingPresence presence, int playerMinDist = 200) { _presence = presence; + _playerMinDist = playerMinDist; } - public void Enter() + public virtual void Enter() { _spawning = false; _spawned = false; + _presence.SpawnOrigin = Position.Empty; + _presence.CurrentRoamingPosition = Position.Empty; + _elapsed = TimeSpan.Zero; _delay = TimeSpan.FromSeconds(_presence.Configuration.RoamingRespawnSeconds * _repawnDelayModifier); _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); } - public void Exit() - { + public void Exit() { } + 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) { @@ -48,13 +62,11 @@ public void Update(TimeSpan time) if (_spawned) { - _presence.StackFSM.Push(new RoamingState(_presence)); + OnSpawned(); return; } - _elapsed += time; - - if (_elapsed < _delay) + if (CheckElapsed(time)) return; _spawning = true; @@ -65,11 +77,11 @@ public void Update(TimeSpan time) }); } - private void SpawnFlocks() + protected virtual void SpawnFlocks() { Position spawnPosition; bool anyPlayersAround; - int range = 200; + int range = _playerMinDist; do { @@ -78,6 +90,17 @@ private void SpawnFlocks() range--; } while (anyPlayersAround && range > 0); + if (anyPlayersAround) + { + _presence.Log("FAILED to resolve spawn position out of range of players: " + spawnPosition); + return; + } + + DoSpawning(spawnPosition); + } + + protected void DoSpawning(Position spawnPosition) + { _presence.SpawnOrigin = spawnPosition; _presence.CurrentRoamingPosition = spawnPosition; _presence.Log("spawn position: " + spawnPosition); @@ -90,39 +113,62 @@ private void SpawnFlocks() } } - public class RoamingState : IState + public class NullRoamingState : IState { - private readonly IRoamingPresence _presence; + protected readonly IRoamingPresence _presence; - public RoamingState(IRoamingPresence presence) + public NullRoamingState(IRoamingPresence presence) { _presence = presence; } - public void Enter() + public virtual void Enter() { } + public virtual void Exit() { } + + protected Npc[] GetAllMembers() { + return _presence.Flocks.GetMembers().ToArray(); } - public void Exit() + 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); } + } + + public class RoamingState : NullRoamingState + { + public RoamingState(IRoamingPresence presence) : base(presence) { } private bool _finding; - public void Update(TimeSpan time) + private bool IsAllNotIdle(Npc[] members) + { + var idleMembersCount = members.Select(m => m.AI.Current).OfType().Count(); + return idleMembersCount < members.Length; + } + + public override void Update(TimeSpan time) { if (_finding) return; - var members = _presence.Flocks.GetMembers().ToArray(); - if (members.Length <= 0) - { - _presence.StackFSM.Pop(); + var members = GetAllMembers(); + if (IsDeadAndExiting(members)) return; - } - var idleMembersCount = members.Select(m => m.AI.Current).OfType().Count(); - if (idleMembersCount < members.Length) + if (IsAllNotIdle(members)) return; _finding = true; diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs index deeb4803f..a390d17cc 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs @@ -10,6 +10,7 @@ namespace Perpetuum.Zones.NpcSystem.Presences { public class Presence : INpcGroup { + public int ID { get; private set; } private ImmutableHashSet _flocks = ImmutableHashSet.Empty; public IPresenceConfiguration Configuration { get; private set; } @@ -25,6 +26,7 @@ public Presence(IZone zone, IPresenceConfiguration configuration) { Zone = zone; Configuration = configuration; + ID = Configuration.ID; } public FlockFactory FlockFactory { private get; set; } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs index a81fa9498..def8a6f51 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs @@ -11,6 +11,7 @@ public enum PresenceType Direct = 6, Interzone = 7, InterzoneRoaming = 8, - DynamicExtended = 9 + DynamicExtended = 9, + ExpiringRandom = 10 } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs index 7f6499a2c..e3a8a580e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/RoamingPresence.cs @@ -18,6 +18,7 @@ public interface IRoamingPresence IEnumerable Flocks { get; } Area Area { get; } void Log(string message); + void OnSpawned(); } public class RoamingPresence : Presence, IRoamingPresence @@ -38,5 +39,7 @@ protected override void OnUpdate(TimeSpan time) { StackFSM.Update(time); } + + public void OnSpawned() { } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs index eb662ebe5..f7922779e 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs @@ -120,6 +120,7 @@ public int CompareTo(Hostile other) public interface IThreatManager { + bool IsThreatened { get; } bool Contains(Unit hostile); void Remove(Hostile hostile); ImmutableSortedSet Hostiles { get; } @@ -152,6 +153,11 @@ public ImmutableSortedSet Hostiles get { return _hostiles.Values.ToImmutableSortedSet(); } } + public bool IsThreatened + { + get { return !_hostiles.IsEmpty; } + } + public bool Contains(Unit unit) { return _hostiles.ContainsKey(unit.Eid); diff --git a/src/Perpetuum/Zones/ZoneExtensions.cs b/src/Perpetuum/Zones/ZoneExtensions.cs index 170cda40a..0deb7077e 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.cs @@ -69,6 +69,7 @@ public static partial class ZoneExtensions { public static List FindWalkableArea(this IZone zone, Area area, int size,double slope = 4.0) { + area = area.Clamp(zone.Size); while (true) { Point startPosition; From b10b71b4de4f455c3270c8675e6211b77e38d3e5 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 4 Aug 2021 22:36:18 -0400 Subject: [PATCH 290/457] Set max sessions (#360) * set max sessions * style, maintainability --- .../ChatCommands/AdminCommandHandlers.cs | 40 ++++++++++++++----- .../Services/Sessions/SessionManager.cs | 2 +- src/Perpetuum/ValueTypeExtensions.cs | 6 +++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 737fd7bf4..a9bf5cdae 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -109,9 +109,9 @@ public static void CheckZoneId(AdminCommandData data, int zoneId) public static void CheckRequiredArgLength(AdminCommandData data, int expectedLength) { - if(data.Command.Args.Length != expectedLength) + if (data.Command.Args.Length != expectedLength) { - SendMessageToAll(data, $"Error - Command expects " + expectedLength.ToString() + " args, but was given " +data.Command.Args.Length.ToString()+ " args"); + SendMessageToAll(data, $"Command expects {expectedLength} args, but was given {data.Command.Args.Length} args"); throw PerpetuumException.Create(ErrorCodes.TooManyOrTooFewArguments); } } @@ -176,6 +176,25 @@ 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 var 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); + } + var 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) { @@ -192,7 +211,7 @@ public static void JumpTo(AdminCommandData data) } catch (Exception ex) { - if(ex is FormatException || ex is ArgumentNullException) + if (ex is FormatException || ex is ArgumentNullException) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); throw; } @@ -440,12 +459,12 @@ public static void FlagPlayerNameOffensive(AdminCommandData data) CheckRequiredArgLength(data, 2); - try + try { characterId = int.Parse(data.Command.Args[0]); isOffensive = bool.Parse(data.Command.Args[1]); } - catch(Exception ex) + catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); @@ -478,7 +497,7 @@ public static void UnlockAllEP(AdminCommandData data) { int accountId; - try + try { accountId = int.Parse(data.Command.Args[0]); } @@ -506,11 +525,12 @@ public static void EPBonusSet(AdminCommandData data) CheckRequiredArgLength(data, 2); - try + try { bonusBoost = int.Parse(data.Command.Args[0]); hours = int.Parse(data.Command.Args[1]); - } catch (Exception ex) + } + catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); @@ -705,7 +725,7 @@ public static void ZoneCreateTerraformLimit(AdminCommandData data) try { mode = data.Command.Args[0]; - if(mode!="clear") + if (mode != "clear") radius = int.Parse(data.Command.Args[1]); } catch (Exception ex) @@ -816,7 +836,7 @@ public static void ZoneAddDecor(AdminCommandData data) throw; } - string cmd() + string cmd() { Dictionary dictionary = new Dictionary() { diff --git a/src/Perpetuum/Services/Sessions/SessionManager.cs b/src/Perpetuum/Services/Sessions/SessionManager.cs index bcb7fec9b..fcc0a75c5 100644 --- a/src/Perpetuum/Services/Sessions/SessionManager.cs +++ b/src/Perpetuum/Services/Sessions/SessionManager.cs @@ -35,7 +35,7 @@ public SessionManager(GlobalConfiguration globalConfiguration,IHostStateService _sessionFactory = sessionFactory; _relayStateService = relayStateService; _steamManager = steamManager; - MaxSessions = 1000; + MaxSessions = 5000; hostStateService.StateChanged += (sender,state) => { diff --git a/src/Perpetuum/ValueTypeExtensions.cs b/src/Perpetuum/ValueTypeExtensions.cs index 94ff6a281..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; From c5cea9b07263a231d1dd4b0b79772b1d976afc33 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 5 Aug 2021 20:45:23 -0400 Subject: [PATCH 291/457] Groundtype fill with fuzzy circles (#361) --- .../PerpetuumBootstrapper.cs | 1 + .../Perpetuum.RequestHandlers.csproj | 1 + .../Zone/ZoneFillGroundTypeRandom.cs | 71 +++++++++++++++++++ src/Perpetuum/Commands.cs | 11 +++ .../ChatCommands/AdminCommandHandlers.cs | 43 ++++++++++- .../Terrains/Materials/Plants/PlantInfo.cs | 7 +- 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/Perpetuum.RequestHandlers/Zone/ZoneFillGroundTypeRandom.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 904387b52..ee018ee9b 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2767,6 +2767,7 @@ private void RegisterZoneRequestHandlers() RegisterZoneRequestHandler(Commands.ZoneDrawBlockingByDefinition); RegisterZoneRequestHandler(Commands.ZoneCleanBlockingByDefinition); RegisterZoneRequestHandler(Commands.ZoneCleanObstacleBlocking); + RegisterZoneRequestHandler(Commands.ZoneFillGroundTypeRandom); diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index d31aa3a4e..c29da95fb 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -624,6 +624,7 @@ + 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/Commands.cs b/src/Perpetuum/Commands.cs index 58b35e26e..2411abfe2 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -1302,6 +1302,17 @@ public static Command GetCommandByText(string commandText) } }; + 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", diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index a9bf5cdae..91de61491 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -937,7 +937,7 @@ public static void ZoneClearLayer(AdminCommandData data) { "layerName", data.Command.Args[0] } }; - string cmd = string.Format("zoneClearLayer:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneClearLayer.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); HandleLocalRequest(data, cmd); } [ChatCommand("ZoneSetPlantSpeed")] @@ -1247,6 +1247,47 @@ public static void ZoneClearGroundType(AdminCommandData data) 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); + var dictionary = new Dictionary(){ + { k.size, radius }, + { k.numberOfRuns, iterations } + }; + var 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("ZoneLockLayers")] public static void ZoneLockLayers(AdminCommandData data) { diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs index 233b2b93a..6cf003cb0 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantInfo.cs @@ -25,7 +25,12 @@ public void Clear() public void ClearGroundType() { - groundType = GroundType.undefined; + SetGroundType(GroundType.undefined); + } + + public void SetGroundType(GroundType type) + { + groundType = type; } public void SetPlant(byte newState, PlantType newPlantType) From 9e84f1af74d8f990559a3861281a51b3bdfe2a8b Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 7 Aug 2021 17:15:05 -0400 Subject: [PATCH 292/457] load just altitude once (#363) --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index ee018ee9b..34cb41a88 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2339,15 +2339,18 @@ private void RegisterTerrains() 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); + 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 originalAltitude = new Layer(LayerType.OriginalAltitude, altitude, 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); + altitudeLayer = new TerraformableAltitude(originalAltitude, blendLayer, altitude); + } + else + { + altitudeLayer = new AltitudeLayer(altitude, size.Width, size.Height); } terrain.Altitude = altitudeLayer; From 2447282b5996a32fb0f5df64c69419e7cb5d4b5f Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 8 Aug 2021 19:34:27 -0400 Subject: [PATCH 293/457] Smooth a zone (#369) * smooth the whole zone * parallelize smoother for whole zone * oof --- .../PerpetuumBootstrapper.cs | 1 + .../Perpetuum.RequestHandlers.csproj | 1 + .../Zone/ZoneDrawRamp.cs | 8 +-- .../Zone/ZoneSmooth.cs | 53 +++++++++++++++++++ src/Perpetuum/Commands.cs | 6 +++ .../ChatCommands/AdminCommandHandlers.cs | 27 ++++++++++ 6 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 src/Perpetuum.RequestHandlers/Zone/ZoneSmooth.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 34cb41a88..a41361579 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2809,6 +2809,7 @@ private void RegisterZoneRequestHandlers() RegisterZoneRequestHandler(Commands.PBSSetReinforceOffset); RegisterZoneRequestHandler(Commands.PBSSetEffect); RegisterZoneRequestHandler(Commands.ZoneDrawRamp); + RegisterZoneRequestHandler(Commands.ZoneSmooth); } diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index c29da95fb..53efee0bb 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -629,6 +629,7 @@ + diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs index 8ac62e04f..a19ff565c 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneDrawRamp.cs @@ -1,15 +1,9 @@ -using Perpetuum.Accounting.Characters; -using Perpetuum.Host.Requests; +using Perpetuum.Host.Requests; using Perpetuum.Players; using Perpetuum.Zones; -using Perpetuum.Zones.Beams; -using Perpetuum.Zones.NpcSystem.Reinforcements; using Perpetuum.Zones.Terrains; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Perpetuum.RequestHandlers.Zone { 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/Commands.cs b/src/Perpetuum/Commands.cs index 2411abfe2..997979ebe 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -98,6 +98,12 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command ZoneSmooth = new Command + { + Text = "zoneSmooth", + AccessLevel = AccessLevel.admin, + }; + public static readonly Command ZoneDisplayMissionSpots = new Command { Text = "zoneDisplayMissionSpots", diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 91de61491..a3fb062d7 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -1288,6 +1288,33 @@ public static void ZoneRandomFillGroundType(AdminCommandData data) 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); + var 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) { From ba29f4f1fa7848c8c565283e9b4641c7d1008fe7 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Aug 2021 04:04:11 -0400 Subject: [PATCH 294/457] Ore npc fix (#378) --- src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 7 ++++++- .../Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index c51f37d0e..7469e5f9d 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -137,7 +137,7 @@ protected virtual void CreateMemberInZone() npc.LootGenerator = gen; npc.HomeRange = HomeRange; - npc.HomePosition = spawnPosition; + npc.HomePosition = GetHomePosition(spawnPosition); npc.CallForHelp = Configuration.IsCallForHelp; OnNpcCreated(npc); @@ -157,6 +157,11 @@ protected virtual Position GetSpawnPosition(Position spawnOrigin) return spawnPosition; } + protected virtual Position GetHomePosition(Position spawnOrigin) + { + return spawnOrigin; + } + public event Action NpcCreated; protected virtual void OnNpcCreated(Npc npc) diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs index 4aeeab36c..d6a3a3507 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs @@ -19,5 +19,14 @@ protected override Position GetSpawnPosition(Position spawnOrigin) } return base.GetSpawnPosition(spawnOrigin); } + + protected override Position GetHomePosition(Position spawnOrigin) + { + if (Presence is DynamicPresenceExtended presence) + { + return presence.DynamicPosition.Clamp(Presence.Zone.Size); + } + return base.GetSpawnPosition(spawnOrigin); + } } } From 863600aef45616a661c3989c748234daaa64fb26 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Aug 2021 04:04:45 -0400 Subject: [PATCH 295/457] PBS terminal info response zone0 fix(#377) --- src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index 2c061af09..b7ec85ef5 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs @@ -142,6 +142,8 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) _pbsReinforceHandler.Init(); _pbsTerritorialVisibilityHelper.Init(); + 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 From 81a321bce258b49bcffcd31a501a71dbe9742db9 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Aug 2021 04:05:37 -0400 Subject: [PATCH 296/457] bomb keepout distance gamma-only (#375) --- src/Perpetuum/Deployers/AreaBombDeployer.cs | 6 ++++++ src/Perpetuum/ErrorCodes.cs | 5 +++-- src/Perpetuum/Zones/DistanceConstants.cs | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) 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/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index b4ade28c7..cae06e29d 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -702,7 +702,8 @@ public enum ErrorCodes InviteOnlyServer, SteamLoginDisabled, AccountAlreadyExists, - RobotWrongType, - PBSTechLevelTooHighForZone + RobotWrongType, // 666 + PBSTechLevelTooHighForZone, + TooCloseToOtherDevice } } diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 7c7847fac..9c0968f6b 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -48,6 +48,8 @@ public class DistanceConstants public const double MAX_NPC_FLOCK_HOME_RANGE = 40; + public const double GAMMA_BOMB_STACK_DISTANCE = 5.0; + public static Dictionary GetEnumDictionary() { var result = new Dictionary(); From 674317e51d261c4a899ac3d81b0c92bee149450e Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Aug 2021 04:06:02 -0400 Subject: [PATCH 297/457] Preset Fitting Fix (#374) --- .../FittingPreset/FittingPresetApply.cs | 1 + 1 file changed, 1 insertion(+) 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)) { From 335e798cc4d9cedef12cdb76e5612f45ff2f6262 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 15 Aug 2021 04:06:14 -0400 Subject: [PATCH 298/457] Ceo takeover fix (#372) * what a bug * simplify diff --- .../Corporations/CorporationVolunteerForCeo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From e1d5d34598ce1819d784f975e03e658e58f35470 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 16 Aug 2021 20:16:39 -0400 Subject: [PATCH 299/457] IZ npc fix (#380) --- .../InterzonePresences/InterzonePresence.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index 6c31508e8..dd00cd40b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -28,6 +28,12 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); } + protected override void OnPresenceExpired() + { + StackFSM.DirectClear(); + base.OnPresenceExpired(); + } + public void OnSpawned() { ResetDynamicDespawnTimer(); @@ -81,14 +87,5 @@ private void OnFlockRemoved() if (Flocks.IsNullOrEmpty()) OnPresenceExpired(); } - - public override void LoadFlocks() - { - base.LoadFlocks(); - foreach (var flock in Flocks) - { - flock.SpawnAllMembers(); - } - } } } \ No newline at end of file From b26a49a31aa0e1710b8b4b21142eed4da9995e79 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 22 Aug 2021 17:23:12 -0400 Subject: [PATCH 300/457] Roamstate exit, handle running task (#382) * cancel token on exit, try/catch on accessing empty flocks * capture task cancellation logic into abstract base class * more context on warn --- .../RandomSpawningExpiringPresence.cs | 24 +---- .../InterzonePresences/InterzonePresence.cs | 2 +- .../PathFinders/FreeRoamingPathFinder.cs | 33 ++++++- .../Presences/PathFinders/RoamingStates.cs | 88 ++++++++++++++----- 4 files changed, 96 insertions(+), 51 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs index 57f7b815d..517aebcf5 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs @@ -63,7 +63,7 @@ protected override void OnSpawned() _presence.StackFSM.Push(new NullRoamingState(_presence)); } - private bool IsInRange(Position position) + protected override bool IsInRange(Position position, int range) { var zone = _presence.Zone; if (zone.Configuration.IsGamma && zone.IsUnitWithCategoryInRange(CategoryFlags.cf_pbs_docking_base, position, BASE_RADIUS)) @@ -77,27 +77,5 @@ private bool IsInRange(Position position) return zone.Players.WithinRange2D(position, PLAYER_RADIUS).Any(); } - - protected override void SpawnFlocks() - { - Position spawnPosition; - bool anyPlayersAround; - int maxRetries = 100; - - do - { - spawnPosition = _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); - anyPlayersAround = IsInRange(spawnPosition); - maxRetries--; - } while (anyPlayersAround && maxRetries > 0); - - if (anyPlayersAround) - { - _presence.Log("FAILED to resolve spawn position out of range of players: " + spawnPosition); - return; - } - - DoSpawning(spawnPosition); - } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs index dd00cd40b..76b13e6e2 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzonePresence.cs @@ -30,7 +30,7 @@ protected override void OnUpdate(TimeSpan time) protected override void OnPresenceExpired() { - StackFSM.DirectClear(); + StackFSM.Clear(); base.OnPresenceExpired(); } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs index 5e156f4ca..1f8356b92 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/FreeRoamingPathFinder.cs @@ -6,6 +6,7 @@ using System.Linq; using Perpetuum.Collections; using Perpetuum.ExportedTypes; +using Perpetuum.Log; using Perpetuum.PathFinders; using Perpetuum.Zones.NpcSystem.Flocks; @@ -22,9 +23,35 @@ public FreeRoamingPathFinder(IZone zone) _direction = FastRandom.NextDouble(); } + private int TryGetMaxHomeRange(IRoamingPresence presence) + { + 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 = presence.Flocks.Max(f => f.HomeRange).Clamp(10, 40); + var homeRange = TryGetMaxHomeRange(presence); var rangeMax = homeRange * 2; var walkableArea = _zone.FindWalkableArea(presence.Area, rangeMax * rangeMax); @@ -33,8 +60,8 @@ public Point FindSpawnPosition(IRoamingPresence 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); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs index a91f89bd4..87d49a08d 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -1,18 +1,53 @@ -using Perpetuum.StateMachines; +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 class SpawnState : IState + 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; private TimeSpan _delay = TimeSpan.Zero; - protected bool _spawning; protected bool _spawned; private double _repawnDelayModifier = 0.0; @@ -24,9 +59,9 @@ public SpawnState(IRoamingPresence presence, int playerMinDist = 200) _playerMinDist = playerMinDist; } - public virtual void Enter() + public void Enter() { - _spawning = false; + OnEnter(); _spawned = false; _presence.SpawnOrigin = Position.Empty; @@ -38,7 +73,10 @@ public virtual void Enter() _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); } - public void Exit() { } + public void Exit() + { + OnExit(); + } protected virtual void OnSpawned() { @@ -57,7 +95,7 @@ private bool CheckElapsed(TimeSpan time) //updated public void Update(TimeSpan time) { - if (_spawning) + if (IsRunningTask) return; if (_spawned) @@ -69,15 +107,15 @@ public void Update(TimeSpan time) if (CheckElapsed(time)) return; - _spawning = true; - Task.Run(() => SpawnFlocks()).ContinueWith(t => - { - _spawned = true; - _spawning = false; - }); + RunTask(() => SpawnFlocks(), t => _spawned = true); } - protected virtual void SpawnFlocks() + protected virtual bool IsInRange(Position position, int range) + { + return _presence.Zone.Players.WithinRange(position, range).Any(); + } + + private void SpawnFlocks() { Position spawnPosition; bool anyPlayersAround; @@ -85,8 +123,13 @@ protected virtual void SpawnFlocks() do { + if (IsCancelled) + { + Logger.Warning("SpawnFlocks() cancelled"); + return; + } spawnPosition = _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); - anyPlayersAround = _presence.Zone.Players.WithinRange(spawnPosition, range).Any(); + anyPlayersAround = IsInRange(spawnPosition, range); range--; } while (anyPlayersAround && range > 0); @@ -99,7 +142,7 @@ protected virtual void SpawnFlocks() DoSpawning(spawnPosition); } - protected void DoSpawning(Position spawnPosition) + private void DoSpawning(Position spawnPosition) { _presence.SpawnOrigin = spawnPosition; _presence.CurrentRoamingPosition = spawnPosition; @@ -113,7 +156,7 @@ protected void DoSpawning(Position spawnPosition) } } - public class NullRoamingState : IState + public class NullRoamingState : CancellableState, IState { protected readonly IRoamingPresence _presence; @@ -122,8 +165,8 @@ public NullRoamingState(IRoamingPresence presence) _presence = presence; } - public virtual void Enter() { } - public virtual void Exit() { } + public virtual void Enter() { OnEnter(); } + public virtual void Exit() { OnExit(); } protected Npc[] GetAllMembers() { @@ -151,8 +194,6 @@ public class RoamingState : NullRoamingState { public RoamingState(IRoamingPresence presence) : base(presence) { } - private bool _finding; - private bool IsAllNotIdle(Npc[] members) { var idleMembersCount = members.Select(m => m.AI.Current).OfType().Count(); @@ -161,7 +202,7 @@ private bool IsAllNotIdle(Npc[] members) public override void Update(TimeSpan time) { - if (_finding) + if (IsRunningTask) return; var members = GetAllMembers(); @@ -171,8 +212,7 @@ public override void Update(TimeSpan time) if (IsAllNotIdle(members)) return; - _finding = true; - Task.Run(() => FindNextRoamingPosition()).ContinueWith(t => _finding = false); + RunTask(() => FindNextRoamingPosition(), t => { }); } private void FindNextRoamingPosition() From 572cc1d9abde8bfc8e05c6b739f2d015cb91da3f Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 22 Aug 2021 17:26:31 -0400 Subject: [PATCH 301/457] Fitting fix (#383) * initial fix, pre-compute fitting limits, isolate optimizations * bring back in the optims, red herring * cancel token on exit, try/catch on accessing empty flocks * capture task cancellation logic into abstract base class * more context on warn --- .../Zone/Containers/EquipModule.cs | 7 +++++- .../Zone/Containers/RemoveModule.cs | 1 + .../Zone/Containers/ZoneChangeModule.cs | 3 +++ src/Perpetuum/Modules/Module.cs | 10 ++++++++ src/Perpetuum/Robots/Robot.Properties.cs | 23 +++++++++++++++---- src/Perpetuum/Robots/Robot.cs | 9 ++++++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs index d26a65ceb..d0eaefcb3 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/EquipModule.cs @@ -14,11 +14,16 @@ public override void DoChange(IZoneRequest request, Player player, Container con var componentType = request.Data.GetOrDefault(k.robotComponent).ToEnum(); var component = player.GetRobotComponentOrThrow(componentType); var slot = request.Data.GetOrDefault(k.slot); - component.MakeSlotFree(slot, container); + 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); module.CheckEnablerExtensionsAndThrowIfFailed(player.Character); + + //Perform pre-fit check for fitting legality + player.CheckEnergySystemAndThrowIfFailed(module); + component.EquipModuleOrThrow(module, slot); } } diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs index 6f391d9bf..74a5c23d2 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/RemoveModule.cs @@ -10,6 +10,7 @@ public override void DoChange(IZoneRequest request, Player player, Container con { 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); } diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index ab68ab54c..e1345ca46 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Transactions; using Perpetuum.Accounting.Characters; @@ -77,6 +78,7 @@ private void EquipChange(IZoneRequest request, Player player, Container containe DoChange(request, player, container); player.OnEquipChange(); player.Initialize(character); + player.CheckEnergySystemAndThrowIfFailed(); // Apply ratios to prevent resetting values @@ -106,6 +108,7 @@ private MessageBuilder BuildMessage(IZoneRequest request, Player player, Contain private static void OnCompleted(Player player, MessageBuilder message) { + player.Initialize(player.Character); player.SendRefreshUnitPacket(); message.Send(); } diff --git a/src/Perpetuum/Modules/Module.cs b/src/Perpetuum/Modules/Module.cs index aae0d9711..3391793fb 100644 --- a/src/Perpetuum/Modules/Module.cs +++ b/src/Perpetuum/Modules/Module.cs @@ -139,5 +139,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/Robots/Robot.Properties.cs b/src/Perpetuum/Robots/Robot.Properties.cs index 6d77b06a4..34f674e9a 100644 --- a/src/Perpetuum/Robots/Robot.Properties.cs +++ b/src/Perpetuum/Robots/Robot.Properties.cs @@ -133,14 +133,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 diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 2a603ce52..8804f83f3 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -354,6 +354,15 @@ public IEnumerable RobotComponents get { return _robotComponents.Value; } } + public void CheckEnergySystemAndThrowIfFailed(Module module, bool isRemoving=false) + { + if (!CheckPowerGridForModule(module, isRemoving)) + throw PerpetuumException.Create(ErrorCodes.OutOfPowergrid); + + if (!CheckCpuForModule(module, isRemoving)) + throw PerpetuumException.Create(ErrorCodes.OutOfCpu); + } + public void CheckEnergySystemAndThrowIfFailed() { if (PowerGrid < 0) From 10a3ec788315461a3190b2568d20b9c006daa3b7 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 3 Sep 2021 13:11:33 -0400 Subject: [PATCH 302/457] Groundtype copy (#385) * new command to copy a zone's groundtype bit array to another zone w/o changing existing plants * a few other enhancements --- .../PerpetuumBootstrapper.cs | 3 +- .../Perpetuum.RequestHandlers.csproj | 1 + .../Zone/ZoneClearLayer.cs | 10 +++ .../ZoneCopyGroundType.cs | 43 ++++++++++ src/Perpetuum/Commands.cs | 11 +++ .../ChatCommands/AdminCommandHandlers.cs | 85 ++++++++++++++++--- 6 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 src/Perpetuum.RequestHandlers/ZoneCopyGroundType.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index a41361579..8a5851baf 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2144,7 +2144,8 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.ZoneSetBaseDetails); RegisterRequestHandler(Commands.ZoneSelfDestruct); RegisterRequestHandler(Commands.ZoneSOS); - + RegisterRequestHandler(Commands.ZoneCopyGroundType); //OPP + RegisterRequestHandler(Commands.ZoneGetZoneObjectDebugInfo); RegisterRequestHandler(Commands.ZoneDrawBlockingByEid); diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index 53efee0bb..a0a729793 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -494,6 +494,7 @@ + diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs index 175a3e7bf..f45477f9f 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneClearLayer.cs @@ -23,6 +23,16 @@ public void HandleRequest(IZoneRequest request) 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/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/Commands.cs b/src/Perpetuum/Commands.cs index 997979ebe..c2cdf63c0 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -1308,6 +1308,17 @@ 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", diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index a3fb062d7..26b527188 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -701,15 +701,25 @@ public static void ZoneCreateIsland(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int lvl); + 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 } - }; + var dictionary = new Dictionary() { { k.low, lvl } }; - string cmd = string.Format("zoneCreateIsland:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + 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!"); @@ -932,13 +942,28 @@ public static void ZoneClearLayer(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - Dictionary dictionary = new Dictionary() - { - { "layerName", data.Command.Args[0] } - }; + 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; + } - string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneClearLayer.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + CheckZoneId(data, zoneId); + + var dictionary = new Dictionary() { { k.layerName, layerName } }; + var 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) @@ -1247,6 +1272,40 @@ public static void ZoneClearGroundType(AdminCommandData data) 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); + var dictionary = new Dictionary() { { k.source, sourceZone }, { k.target, targetZone } }; + var 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) { @@ -1298,7 +1357,7 @@ public static void ZoneUpdateSlope(AdminCommandData data) try { - if(data.Command.Args.Length > 0) + if (data.Command.Args.Length > 0) zoneId = int.Parse(data.Command.Args[0]); } catch (Exception ex) From 89bd99de4ccb7c6473d48af5911791f24c1a0a35 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 3 Sep 2021 13:11:57 -0400 Subject: [PATCH 303/457] gamma tier zone effects (#388) --- src/Perpetuum.ExportedTypes/EffectType.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 9c63c34a2..0881f0614 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -115,6 +115,10 @@ public enum EffectType effect_night_overcast = 112, effect_weather_good = 113, effect_weather_bad = 114, - effect_stronghold_despawn_timer = 115 + effect_stronghold_despawn_timer = 115, + effect_gamma_t0 = 116, + effect_gamma_t1 = 117, + effect_gamma_t2 = 118, + effect_gamma_t3 = 119 } } From 32987ba0c8a0e4fe4e809f46bb7479afa7a04544 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:11:04 -0400 Subject: [PATCH 304/457] Mission difficulty (#390) --- .../MissionEngine/MissionTargets/RandomTargetObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs b/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs index 513bb27c1..da2419c0d 100644 --- a/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs +++ b/src/Perpetuum/Services/MissionEngine/MissionTargets/RandomTargetObjects.cs @@ -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; From 2ece680e0bab622e4c9a09aa03f5af72ab38f616 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:11:21 -0400 Subject: [PATCH 305/457] prox probe limits (#389) --- src/Perpetuum/Zones/DistanceConstants.cs | 5 +++-- src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 9c0968f6b..962c2592c 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -23,8 +23,9 @@ public class DistanceConstants 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_FROM_BASE = 0.0; - public const double PROXIMITY_PROBE_DEPLOY_RANGE_FROM_TELEPORT = 0.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; diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs index 58b0246c6..796845818 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs @@ -40,6 +40,7 @@ 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) From 59cbd5d3a6f826b45dab2376197dfe279773ee55 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 18 Sep 2021 23:00:01 -0400 Subject: [PATCH 306/457] Misc dev enhancements: IZ npc null check + command improvements (#393) * null check * enhancing and fixing a few zone admin commands --- .../Zone/ZoneCreateTerraformLimit.cs | 53 ++++++++++++------- .../ChatCommands/AdminCommandHandlers.cs | 14 +++-- .../InterzonePresences/InterzoneGroup.cs | 5 ++ 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs index 5450ab154..861017d48 100644 --- a/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs +++ b/src/Perpetuum.RequestHandlers/Zone/ZoneCreateTerraformLimit.cs @@ -1,7 +1,6 @@ using Perpetuum.Host.Requests; using Perpetuum.Zones; -using Perpetuum.Zones.Terrains; -using System.Drawing; +using System.Threading.Tasks; namespace Perpetuum.RequestHandlers.Zone { @@ -9,39 +8,55 @@ public class ZoneCreateTerraformLimit : IRequestHandler { private void Clear(IZoneRequest request) { - request.Zone.Terrain.Controls.UpdateAll((x, y, c) => + 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) => { - c.TerraformProtected = false; - return c; + 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, zone.Terrain.Controls.Width); - var x1 = (center.X + radius).Clamp(0, zone.Terrain.Controls.Width); - var y0 = (center.Y - radius).Clamp(0, zone.Terrain.Controls.Height); - var y1 = (center.Y + radius).Clamp(0, zone.Terrain.Controls.Height); - for (var y = y0; y < y1; y++) + 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) => { - for (var x = x0; x < x1; x++) + foreach (var p in workArea.GetPositions()) { - var p = new Point(x, y); - if (center.Distance(p) < radius) + if(!p.intX.IsInRange(0, maxY) || !p.intY.IsInRange(0, maxY)) + { + continue; + } + else if (center.Distance(p) < radius) { - zone.Terrain.Controls.UpdateValue(x, y, (c) => - { - c.TerraformProtected = true; - return c; - }); + var c = zone.Terrain.Controls[p.intX, p.intY]; + c.TerraformProtected = true; + c.PBSTerraformProtected = true; + zone.Terrain.Controls[p.intX, p.intY] = c; } } - } + }); } } diff --git a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 26b527188..294ffdbee 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -730,6 +730,7 @@ public static void ZoneCreateTerraformLimit(AdminCommandData data) if (!IsDevModeEnabled(data)) return; + int zoneId = data.Sender.ZoneId ?? -1; int radius = 0; string mode; try @@ -737,6 +738,7 @@ public static void ZoneCreateTerraformLimit(AdminCommandData data) 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) { @@ -744,14 +746,15 @@ public static void ZoneCreateTerraformLimit(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); throw; } + CheckZoneId(data, zoneId); - Dictionary dictionary = new Dictionary() + var dictionary = new Dictionary() { { k.distance, radius }, { k.mode, mode } }; - string cmd = string.Format("ZoneCreateTerraformLimit:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + 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!"); @@ -762,6 +765,7 @@ public static void ZoneSetLayerWithBitMap(AdminCommandData data) if (!IsDevModeEnabled(data)) return; + int zoneId = data.Sender.ZoneId ?? -1; string fileName; int flagValue; try @@ -769,6 +773,7 @@ public static void ZoneSetLayerWithBitMap(AdminCommandData data) var 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) { @@ -776,14 +781,15 @@ public static void ZoneSetLayerWithBitMap(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); throw; } + CheckZoneId(data, zoneId); - Dictionary dictionary = new Dictionary() + var dictionary = new Dictionary() { { k.file, fileName }, { k.flags, flagValue } }; - string cmd = string.Format("ZoneSetLayerWithBitMap:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + 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!"); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs index 03aa6b629..ed59c9043 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/InterzonePresences/InterzoneGroup.cs @@ -52,6 +52,11 @@ 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(); From 32964394520c578fb3e725590cec22f7cb1ab130 Mon Sep 17 00:00:00 2001 From: Ghroom Date: Wed, 22 Sep 2021 07:30:35 +0200 Subject: [PATCH 307/457] issue 288: session count must count only authenticated users --- src/Perpetuum/Services/Relay/ServerStateInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 68faa0be10b51b0cc2671ea7dde8ae63c12315ff Mon Sep 17 00:00:00 2001 From: Ghroom Date: Wed, 22 Sep 2021 07:50:24 +0200 Subject: [PATCH 308/457] issue 312: Make a HarvestingLogHandler and HarvestLog table --- .../PerpetuumBootstrapper.cs | 2 + src/Perpetuum/Zones/HarvestLogHandler.cs | 94 +++++++++++++++++++ src/Perpetuum/Zones/IZone.cs | 1 + src/Perpetuum/Zones/Zone.cs | 2 + 4 files changed, 99 insertions(+) create mode 100644 src/Perpetuum/Zones/HarvestLogHandler.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 8a5851baf..80b3f84d7 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2531,6 +2531,7 @@ private void RegisterZones() _builder.RegisterType().As(); _builder.RegisterType(); + _builder.RegisterType(); _builder.RegisterType().As().SingleInstance(); void RegisterZone(ZoneType type) where T:Zone @@ -2577,6 +2578,7 @@ 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.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); 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/IZone.cs b/src/Perpetuum/Zones/IZone.cs index edad57087..4a40afd96 100644 --- a/src/Perpetuum/Zones/IZone.cs +++ b/src/Perpetuum/Zones/IZone.cs @@ -52,6 +52,7 @@ public interface IZone PBSHighwayHandler HighwayHandler { get; } TerraformHandler TerraformHandler { get; } MiningLogHandler MiningLogHandler { get; } + HarvestLogHandler HarvestLogHandler { get; } IRelicManager RelicManager { get; } IZoneEffectHandler ZoneEffectHandler { get; } diff --git a/src/Perpetuum/Zones/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 91f16b1cc..983193fa2 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -65,6 +65,7 @@ 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; } @@ -332,6 +333,7 @@ public override void Update(TimeSpan time) RiftManager?.Update(time); RelicManager?.Update(time); MiningLogHandler.Update(time); + HarvestLogHandler.Update(time); MeasureUpdate(time); } From 6f6cd1365d164afc8b991037526a8c90951ba385 Mon Sep 17 00:00:00 2001 From: Ghroom Date: Wed, 22 Sep 2021 17:50:24 +0200 Subject: [PATCH 309/457] add harvest log (#312) --- src/Perpetuum/Modules/HarvesterModule.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Modules/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index 0f064ae67..a04d3f54f 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Linq; using System.Transactions; @@ -137,7 +137,11 @@ private void DoHarvesting(IZone zone) container.AddItem(item, true); - player.MissionHandler.EnqueueMissionEventInfo(new HarvestPlantEventInfo(player, extractedMaterial.Definition, extractedMaterial.Quantity, terrainLock.Location)); + var extractedHarvestDefinition = extractedMaterial.Definition; + var extractedQuantity = extractedMaterial.Quantity; + + player.MissionHandler.EnqueueMissionEventInfo(new HarvestPlantEventInfo(player, extractedHarvestDefinition, extractedQuantity, terrainLock.Location)); + player.Zone?.HarvestLogHandler.EnqueueHarvestLog(extractedHarvestDefinition, extractedQuantity); } //everything went ok, save container From 26e84dda6e57ebed25d8ff72fe1badad6a84ac58 Mon Sep 17 00:00:00 2001 From: Ghroom Date: Thu, 23 Sep 2021 10:45:45 +0200 Subject: [PATCH 310/457] added csproj file to include the new class --- src/Perpetuum/Perpetuum.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index afcb72df5..d35f89d20 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -643,6 +643,7 @@ + From 58b73ab47a00f5e7168549facc24aaffd0b5b652 Mon Sep 17 00:00:00 2001 From: Steve McQueeney Date: Thu, 30 Sep 2021 20:42:21 +0200 Subject: [PATCH 311/457] Issue 214 has been solved --- src/Perpetuum/Modules/Weapons/WeaponModule.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 5005644d6..59faa97d1 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -72,11 +72,6 @@ public void VisitUnitLock(UnitLock unitLock) victim.IsInvulnerable.ThrowIfTrue(ErrorCodes.TargetIsInvulnerable); - if (victim is Rift && (_weapon.ParentRobot.Zone is StrongHoldZone)) - { - throw new PerpetuumException(ErrorCodes.TargetIsNonAttackable); - } - _weapon.ConsumeAmmo(); var result = _weapon.GetLineOfSight(victim); From 52116156386aa50e628188a41b177c58ecb9843e Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Thu, 30 Sep 2021 20:57:39 -0700 Subject: [PATCH 312/457] added scaling value for plant repopulation timer --- .../Zones/Terrains/Materials/Plants/PlantHandler.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index aa28008b6..21eacdddf 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -21,6 +21,8 @@ public class PlantHandler : IPlantHandler public delegate IPlantHandler Factory(IZone zone); private const int AREA_SIZE = 32; + // The number of 32x32 cubes across the zone max width of 2048x2048 + private const int TIME_SCALING_BASE = 64; private readonly IZone _zone; private readonly TimeSpan _natureSleepAmount = TimeSpan.FromSeconds(7); @@ -41,6 +43,8 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas + // We want to scale the time it takes to repopulate a zone + AddTimerScaling(_areaAmount); WorkArea = zone.Size.ToArea(); ScannerMode = PlantScannerMode.Scanner; @@ -76,6 +80,12 @@ public void Update(TimeSpan time) }); } + // https://github.com/OpenPerpetuum/PerpetuumServer/issues/304 + private void AddTimerScaling(int zoneWidth) + { + _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(TIME_SCALING_BASE / zoneWidth)); + } + private void ProcessPlants() { switch (ScannerMode) From 393e53f5caf09cd09111a4695e0481397cc24401 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Thu, 30 Sep 2021 21:05:19 -0700 Subject: [PATCH 313/457] bug fix for timer scaling value --- src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index 21eacdddf..dd3c8a56e 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -83,7 +83,7 @@ public void Update(TimeSpan time) // https://github.com/OpenPerpetuum/PerpetuumServer/issues/304 private void AddTimerScaling(int zoneWidth) { - _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(TIME_SCALING_BASE / zoneWidth)); + _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds((TIME_SCALING_BASE / zoneWidth) * 7)); } private void ProcessPlants() From 92d39f52bdac89cca0dc4f0b5c60c6b61b847cf4 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 9 Oct 2021 22:16:54 -0400 Subject: [PATCH 314/457] Npc infestation: growing spawns of npcs (#391) --- .../PerpetuumBootstrapper.cs | 13 ++ src/Perpetuum/ErrorCodes.cs | 3 +- src/Perpetuum/Perpetuum.csproj | 6 + src/Perpetuum/Zones/DistanceConstants.cs | 2 +- .../FlockEscalationSelector.cs | 85 ++++++++++ .../IRandomStaticPresence.cs | 4 + .../RandomSpawningExpiringPresence.cs | 52 ++---- .../StaticExpiringFlock.cs | 5 +- .../StaticSpawnState.cs | 41 +++++ .../GrowingNPCBasePresence.cs | 29 ++++ .../GrowingPresences/GrowingPresence.cs | 57 +++++++ .../GrowingPresences/GrowthStates.cs | 156 ++++++++++++++++++ .../Presences/IPresenceConfiguration.cs | 1 + .../Presences/PathFinders/RoamingStates.cs | 34 ++-- .../Presences/PresenceConfiguration.cs | 2 + .../Presences/PresenceConfigurationReader.cs | 1 + .../Zones/NpcSystem/Presences/PresenceType.cs | 4 +- src/Perpetuum/Zones/PBS/PBSDeployer.cs | 4 + 18 files changed, 446 insertions(+), 53 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/FlockEscalationSelector.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/IRandomStaticPresence.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticSpawnState.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingNPCBasePresence.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowingPresence.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/GrowingPresences/GrowthStates.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 8a5851baf..c7117449a 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -184,6 +184,8 @@ using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; using Perpetuum.Services.Strongholds; using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; +using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; namespace Perpetuum.Bootstrapper { @@ -1462,6 +1464,13 @@ public void RegisterNpcs() .SingleInstance() .OnActivated(e => e.Instance.Init()); + _builder.RegisterType().As().SingleInstance(); + + _builder.RegisterType() + .As() + .SingleInstance() + .OnActivated(e => e.Instance.Init()); + _builder.RegisterType().As(); _builder.RegisterType().As(); _builder.RegisterType().As(); @@ -1507,6 +1516,8 @@ public void RegisterNpcs() RegisterFlock(PresenceType.FreeRoaming); RegisterFlock(PresenceType.Interzone); RegisterFlock(PresenceType.InterzoneRoaming); + RegisterFlock(PresenceType.EscalatingRandomPresence); + RegisterFlock(PresenceType.GrowingNPCBasePresence); RegisterPresence(PresenceType.Normal); RegisterPresence(PresenceType.Direct).OnActivated(e => @@ -1522,6 +1533,8 @@ public void RegisterNpcs() RegisterPresence(PresenceType.FreeRoaming); RegisterPresence(PresenceType.Interzone); RegisterPresence(PresenceType.InterzoneRoaming); + RegisterPresence(PresenceType.EscalatingRandomPresence); + RegisterPresence(PresenceType.GrowingNPCBasePresence); _builder.Register(x => { diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index cae06e29d..c4f0fadfc 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -704,6 +704,7 @@ public enum ErrorCodes AccountAlreadyExists, RobotWrongType, // 666 PBSTechLevelTooHighForZone, - TooCloseToOtherDevice + TooCloseToOtherDevice, + TooCloseToNPCBase, } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index afcb72df5..6f5a5389b 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -716,8 +716,14 @@ + + + + + + diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 962c2592c..8070db1df 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -48,7 +48,7 @@ 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 static Dictionary GetEnumDictionary() 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 index 517aebcf5..d8dbd10fa 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/RandomSpawningExpiringPresence.cs @@ -1,21 +1,17 @@ using Perpetuum.StateMachines; using Perpetuum.Zones.NpcSystem.Presences.PathFinders; using System; -using System.Linq; using System.Drawing; -using Perpetuum.ExportedTypes; -using Perpetuum.Units.DockingBases; -using Perpetuum.Units; -using Perpetuum.Zones.Teleporting; +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, IRoamingPresence + public class RandomSpawningExpiringPresence : ExpiringPresence, IRandomStaticPresence, IRoamingPresence { - public StackFSM StackFSM { get; } + public StackFSM StackFSM { get; protected set; } public Position SpawnOrigin { get; set; } public IRoamingPathFinder PathFinder { get; set; } public override Area Area => Configuration.Area; @@ -26,6 +22,11 @@ public RandomSpawningExpiringPresence(IZone zone, IPresenceConfiguration configu if (Configuration.DynamicLifeTime != null) LifeTime = TimeSpan.FromSeconds((int)Configuration.DynamicLifeTime); + InitStateMachine(); + } + + protected virtual void InitStateMachine() + { StackFSM = new StackFSM(); StackFSM.Push(new StaticSpawnState(this)); } @@ -33,49 +34,24 @@ public RandomSpawningExpiringPresence(IZone zone, IPresenceConfiguration configu protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); - StackFSM.Update(time); + StackFSM?.Update(time); } protected override void OnPresenceExpired() { - ResetDynamicDespawnTimer(); + StackFSM?.Clear(); foreach (var flock in Flocks) { flock.RemoveAllMembersFromZone(true); } - } - - public void OnSpawned() - { ResetDynamicDespawnTimer(); + InitStateMachine(); + base.OnPresenceExpired(); } - } - public class StaticSpawnState : SpawnState - { - private readonly int BASE_RADIUS = 300; - private readonly int PLAYER_RADIUS = 150; - public StaticSpawnState(IRoamingPresence presence, int playerMinDist = 200) : base(presence, playerMinDist) { } - - protected override void OnSpawned() + public virtual 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, BASE_RADIUS)) - return true; - else if (zone.GetStaticUnits().OfType().WithinRange2D(position, BASE_RADIUS).Any()) - return true; - else if (zone.GetStaticUnits().OfType().WithinRange2D(position, PLAYER_RADIUS).Any()) - return true; - else if (zone.PresenceManager.GetPresences().OfType().Where(p => p.SpawnOrigin.IsInRangeOf2D(position, BASE_RADIUS)).Any()) - return true; - - return zone.Players.WithinRange2D(position, PLAYER_RADIUS).Any(); + ResetDynamicDespawnTimer(); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs index f2f4a75d0..a6fc056e3 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/ExpiringStaticPresence/StaticExpiringFlock.cs @@ -1,4 +1,5 @@ using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; namespace Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence { @@ -8,7 +9,7 @@ public StaticExpiringFlock(IFlockConfiguration configuration, Presence presence) protected override bool IsPresenceInSpawningState() { - if (Presence is RandomSpawningExpiringPresence pres) + if (Presence is IRandomStaticPresence pres) { return pres.StackFSM.Current is StaticSpawnState; } @@ -17,7 +18,7 @@ protected override bool IsPresenceInSpawningState() protected override Position GetSpawnPosition(Position spawnOrigin) { - if (Presence is RandomSpawningExpiringPresence pres) + if (Presence is IRandomStaticPresence pres) { spawnOrigin = pres.SpawnOrigin.Clamp(Presence.Zone.Size); } 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 index 4551ffa7f..bd22f1b28 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/IPresenceConfiguration.cs @@ -22,6 +22,7 @@ public interface IPresenceConfiguration bool IsRespawnAllowed { get; } int? InterzoneGroupId { get; } + int? GrowthSeconds { get; } int ZoneID { get; } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs index 87d49a08d..6acf6ae6b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -46,10 +46,10 @@ protected void RunTask(Action action, Action continuation) public class SpawnState : CancellableState, IState { protected readonly IRoamingPresence _presence; - private TimeSpan _delay = TimeSpan.Zero; + protected TimeSpan _delay = TimeSpan.Zero; protected bool _spawned; - private double _repawnDelayModifier = 0.0; + protected double _repawnDelayModifier = 0.0; protected readonly int _playerMinDist; @@ -59,6 +59,12 @@ public SpawnState(IRoamingPresence presence, int playerMinDist = 200) _playerMinDist = playerMinDist; } + protected virtual void SetSpawnDelay() + { + _delay = TimeSpan.FromSeconds(_presence.Configuration.RoamingRespawnSeconds * _repawnDelayModifier); + _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); + } + public void Enter() { OnEnter(); @@ -69,8 +75,7 @@ public void Enter() _elapsed = TimeSpan.Zero; - _delay = TimeSpan.FromSeconds(_presence.Configuration.RoamingRespawnSeconds * _repawnDelayModifier); - _repawnDelayModifier = FastRandom.NextDouble(1.0, 2.0); + SetSpawnDelay(); } public void Exit() @@ -115,12 +120,21 @@ 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; - bool anyPlayersAround; int range = _playerMinDist; - + bool isValidSpawnLocation; do { if (IsCancelled) @@ -128,12 +142,12 @@ private void SpawnFlocks() Logger.Warning("SpawnFlocks() cancelled"); return; } - spawnPosition = _presence.PathFinder.FindSpawnPosition(_presence).ToPosition(); - anyPlayersAround = IsInRange(spawnPosition, range); + spawnPosition = FindSpawnPosition(); + isValidSpawnLocation = IsValidSpawnPosition(spawnPosition, range); range--; - } while (anyPlayersAround && range > 0); + } while (!isValidSpawnLocation && range > 0); - if (anyPlayersAround) + if (!isValidSpawnLocation) { _presence.Log("FAILED to resolve spawn position out of range of players: " + spawnPosition); return; diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs index d65496ec2..b6810405b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfiguration.cs @@ -24,6 +24,8 @@ public class PresenceConfiguration : IPresenceConfiguration public int? InterzoneGroupId { get; set; } + public int? GrowthSeconds { get; set; } + public int ZoneID { get; set; } public Position RandomCenter => new Position((double)RandomCenterX, (double)RandomCenterY); diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs index 94fab3175..95f21e312 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceConfigurationReader.cs @@ -46,6 +46,7 @@ private static IPresenceConfiguration CreatePresenceConfigurationFromRecord(IDat 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/PresenceType.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs index def8a6f51..d872d3802 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceType.cs @@ -12,6 +12,8 @@ public enum PresenceType Interzone = 7, InterzoneRoaming = 8, DynamicExtended = 9, - ExpiringRandom = 10 + ExpiringRandom = 10, + EscalatingRandomPresence = 11, + GrowingNPCBasePresence = 12 } } \ No newline at end of file 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; From 5651361824d9f85f749fee1fc9488dd5dcee0f88 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 9 Oct 2021 22:17:13 -0400 Subject: [PATCH 315/457] new effects, new rule for checking on teleport, robustify presence loading on startup (#407) --- src/Perpetuum.ExportedTypes/AggregateField.cs | 6 ++++-- src/Perpetuum.ExportedTypes/EffectType.cs | 3 ++- src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs | 1 + src/Perpetuum/Units/Unit.cs | 2 ++ src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs | 5 +++++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 73019d421..f044e632f 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -344,7 +344,8 @@ public enum AggregateField module_missile_range_modifier = 586, optimal_range = 255, optimal_range_modifier = 256, - pbs_turret_core_per_sec = 652, + 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, @@ -386,7 +387,8 @@ public enum AggregateField projectile_cycle_time_modifier = 297, projectile_falloff_modifier = 298, projectile_optimal_range_modifier = 299, - railgun_ammo_cycle_time_modifier = 639, + 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, diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 0881f0614..97ef7bccd 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -119,6 +119,7 @@ public enum EffectType effect_gamma_t0 = 116, effect_gamma_t1 = 117, effect_gamma_t2 = 118, - effect_gamma_t3 = 119 + effect_gamma_t3 = 119, + effect_gamma_tc = 120 } } diff --git a/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs b/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs index 005c359a5..65fe29740 100644 --- a/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs +++ b/src/Perpetuum.RequestHandlers/Zone/TeleportUse.cs @@ -139,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); } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 71f97eaa9..e59ed4393 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -248,6 +248,8 @@ public bool IsInvulnerable public bool HasDespawnEffect => EffectHandler.ContainsEffect(EffectType.effect_despawn_timer); + public bool HasNoTeleportWhilePVP => EffectHandler.Effects.Any(e => e.PropertyModifiers.Any(p => p.Field == AggregateField.pvp_no_teleport)); + public Position WorldPosition { get { return Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; } diff --git a/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs b/src/Perpetuum/Zones/NpcSystem/Presences/PresenceManager.cs index ca031cd60..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); } From 9eafd00e0026d29f0393cad7e4b55ea6dcba7c90 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 14 Oct 2021 22:17:40 -0400 Subject: [PATCH 316/457] Tech level'd construction blocks (#410) * construction blocks mapped to each tech level of structure * unused var --- src/Perpetuum.ExportedTypes/AggregateField.cs | 3 +- .../DefinitionNames.cs | 4 + src/Perpetuum/ErrorCodes.cs | 1 + src/Perpetuum/Perpetuum.csproj | 1 + .../Zones/PBS/ConstructionAmmoHelpers.cs | 74 +++++++++++++++++++ src/Perpetuum/Zones/PBS/ConstructionModule.cs | 9 ++- src/Perpetuum/Zones/PBS/PBSHelper.cs | 36 +-------- src/Perpetuum/Zones/PBS/PBSInterfaces.cs | 2 + src/Perpetuum/Zones/PBS/PBSObjectHelper.cs | 2 +- 9 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 src/Perpetuum/Zones/PBS/ConstructionAmmoHelpers.cs diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index f044e632f..feaaef482 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -35,7 +35,8 @@ public enum AggregateField blob_level_low_modifier = 592, chemical_damage_to_core_modifier = 621, construction_charge_amount = 647, - control_speed_boost_time_add = 505, + construction_charge_techmax = 666, + control_speed_boost_time_add = 505, core_added = 24, core_current = 574, core_max = 26, diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index ecb8228b1..19e2d4673 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"; diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index c4f0fadfc..048c084a3 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -706,5 +706,6 @@ public enum ErrorCodes PBSTechLevelTooHighForZone, TooCloseToOtherDevice, TooCloseToNPCBase, + TechLevelTooLow } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 6f5a5389b..6c8fc8aa8 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -747,6 +747,7 @@ + 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/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 5fc160345..09ddff4e9 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -876,7 +876,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(() => @@ -888,38 +888,6 @@ private static ErrorCodes HandleNodeDead(IZone zone, T pbsNode, Unit killer) return ec; } - public const int CONSTRUCTION_AMMO_DEFINITION = 4658; //TODO: Fetch this from DB - - 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"); @@ -950,7 +918,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 diff --git a/src/Perpetuum/Zones/PBS/PBSInterfaces.cs b/src/Perpetuum/Zones/PBS/PBSInterfaces.cs index 7ba48681a..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; @@ -66,6 +67,7 @@ public interface IPBSObject bool IsOrphaned { get; set; } int ZoneIdCached { get; } + EntityDefault ED { get; } event Action OrphanedStateChanged; diff --git a/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs b/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs index 02f25eb5c..8b49cb7e3 100644 --- a/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs @@ -260,7 +260,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; From 6e5eca0b30474e280c36b6c2b611924ff1692a5b Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 15 Oct 2021 01:31:39 -0400 Subject: [PATCH 317/457] Staging PBS base: expires + other unique rules (#408) * expiring pbs base, fix bug where system storage node was getting deleted twice on pbs death * expiring base only buildable on red zones on gamma * distance checks, quantity check exceptions, separate count, red zone build check only * some refinements, comments, cleanup --- .../PerpetuumBootstrapper.cs | 2 + .../DefinitionNames.cs | 1 + .../EntityRepositoryExtensions.cs | 8 ++ src/Perpetuum/ErrorCodes.cs | 5 +- src/Perpetuum/Perpetuum.csproj | 1 + .../Facilities/ProductionFacility.cs | 2 +- src/Perpetuum/Zones/DistanceConstants.cs | 2 + .../DockingBases/ExpiringPBSDockingBase.cs | 82 +++++++++++++++++++ .../Zones/PBS/DockingBases/PBSDockingBase.cs | 4 +- src/Perpetuum/Zones/PBS/PBSEgg.cs | 13 ++- src/Perpetuum/Zones/PBS/PBSHelper.cs | 71 ++++++++++------ src/Perpetuum/Zones/PBS/PBSObjectHelper.cs | 7 +- 12 files changed, 167 insertions(+), 31 deletions(-) create mode 100644 src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index c7117449a..6e8732065 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -862,6 +862,7 @@ private void RegisterEntities() RegisterEntity(); RegisterUnit(); RegisterUnit(); + RegisterUnit(); RegisterUnit().OnActivated(e => { #if (DEBUG) @@ -1122,6 +1123,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index 19e2d4673..e0a1ad26e 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -3971,6 +3971,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"; 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/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index 048c084a3..f28d8eb1d 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -706,6 +706,9 @@ public enum ErrorCodes PBSTechLevelTooHighForZone, TooCloseToOtherDevice, TooCloseToNPCBase, - TechLevelTooLow + OnlyBuildableOnTerraformProtected, + TechLevelTooLow, + MaxExpiringBasePerZoneReached, + MaxExpiringBasePerZonePerCorpReached } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 6c8fc8aa8..849fc3af0 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -747,6 +747,7 @@ + diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs index df17f0b28..c0aa128bf 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs @@ -255,7 +255,7 @@ protected void CreateSystemStorage() protected void RemoveStorage() { - Repository.DeleteTree(StorageEid); + Repository.DeleteTreeButNotRoot(StorageEid); } protected void MoveItemsToStorage(params Item[] itemsToReserve) diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 8070db1df..aa0e8271c 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -51,6 +51,8 @@ public class DistanceConstants 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 static Dictionary GetEnumDictionary() { var result = new Dictionary(); diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs new file mode 100644 index 000000000..1643c2bec --- /dev/null +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -0,0 +1,82 @@ +using Perpetuum.Common; +using Perpetuum.Groups.Corporations; +using Perpetuum.Items.Templates; +using Perpetuum.Services.Channels; +using Perpetuum.Services.MarketEngine; +using Perpetuum.Services.Sparks.Teleports; +using Perpetuum.Units; +using Perpetuum.Units.DockingBases; +using System; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.PBS.DockingBases +{ + public class ExpiringPBSDockingBase : PBSDockingBase + { + private IUnitDespawnHelper _despawnHelper; + private TimeSpan _aliveElapsed; + private readonly TimeSpan _alertPeriod = TimeSpan.FromHours(1); + private const int MIN_LIFE_HOURS = 72; + private bool _alerted; + + 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) + { + } + + public TimeSpan LifeTime + { + get + { + return TimeSpan.FromHours(ED.Config.lifeTime ?? MIN_LIFE_HOURS); + } + } + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + _despawnHelper = UnitDespawnHelper.Create(this, LifeTime); + _despawnHelper.DespawnStrategy = Kill; + + base.OnEnterZone(zone, enterType); + } + + protected override void OnUpdate(TimeSpan time) + { + if (!_alerted) + WarnIfAboutToExpire(time); + + _despawnHelper?.Update(time, this); + + base.OnUpdate(time); + } + + private void WarnIfAboutToExpire(TimeSpan time) + { + _aliveElapsed += time; + if (LifeTime - _aliveElapsed < _alertPeriod) + { + Task.Run(() => _pbsObjectHelper.SendAttackAlert()); + _alerted = true; + } + } + + public override ErrorCodes IsDeconstructAllowed() + { + return ErrorCodes.DockingBaseNotSetToDeconstruct; + } + } +} diff --git a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index b7ec85ef5..ddb9e3775 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; @@ -370,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)) diff --git a/src/Perpetuum/Zones/PBS/PBSEgg.cs b/src/Perpetuum/Zones/PBS/PBSEgg.cs index c222ac06f..442577bb3 100644 --- a/src/Perpetuum/Zones/PBS/PBSEgg.cs +++ b/src/Perpetuum/Zones/PBS/PBSEgg.cs @@ -153,12 +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; } diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 09ddff4e9..9bc155f7b 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,16 +113,37 @@ public static int GetCapsuleDefinitionByPBSObjectDefinition(int nodeDefinition) } + public static ErrorCodes ValidateExpiringPBSDockingBasePlacement(IZone zone, Position position, long owner, EntityDefault definition) + { + var expiringBases = zone.Units.Where(u => u is ExpiringPBSDockingBase); + + if (expiringBases.Count() >= MAX_EXPIRING_BASES_PER_ZONE) + { + return ErrorCodes.MaxExpiringBasePerZoneReached; + } + else if (expiringBases.Count(u => u.Owner == 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); + } + public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position position, long owner, - EntityDefault dockingbaseEntityDefault) + EntityDefault definition) { - //current total number of bases per zone - var baseCountPerZone = zone.Units.Count(u => u is PBSDockingBase); + //Get PBS docking bases - excluding Expiring types + var bases = zone.Units.Where(u => u is PBSDockingBase && !(u is ExpiringPBSDockingBase)); + //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; } @@ -129,19 +151,22 @@ public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position po if (maxBasesPerZone > MAX_BASES_PER_CORP_PER_ZONE) { //only a set number of bases for one corporation - var baseCountPerCorporation = zone.Units.Count(u => u is PBSDockingBase && u.Owner == owner); - - if (baseCountPerCorporation + 1 > MAX_BASES_PER_CORP_PER_ZONE) + if (bases.Count(u => u.Owner == owner) >= MAX_BASES_PER_CORP_PER_ZONE) { return ErrorCodes.MaxDockingBasePerZonePerCorporationReached; } } - var typeExclusiveRange = dockingbaseEntityDefault.Config.typeExclusiveRange; + return CheckRangeToOtherBases(zone, position, definition); + } + + private static ErrorCodes CheckRangeToOtherBases(IZone zone, Position position, EntityDefault definition) + { + var typeExclusiveRange = definition.Config.typeExclusiveRange; if (typeExclusiveRange == null) { - Logger.Error("no typeExclusiveRange defined for " + dockingbaseEntityDefault); + Logger.Error("no typeExclusiveRange defined for " + definition); return ErrorCodes.WTFErrorMedicalAttentionSuggested; } @@ -150,8 +175,6 @@ public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position po : ErrorCodes.NoError; } - - public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, EntityDefault entityDefault) { List badSlopes; @@ -172,30 +195,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)) @@ -231,9 +256,7 @@ public static ErrorCodes CheckZoneForDeployment(IZone zone, Position position, E sessionError = ErrorCodes.TerrainTooSteepAndBlockedTilesWasFound; } - return sessionError; - } /// diff --git a/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs b/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs index 8b49cb7e3..6701f6259 100644 --- a/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSObjectHelper.cs @@ -67,7 +67,7 @@ private void OnUnitDamageTaken(Unit unit, Unit attacker, DamageTakenEventArgs e) { if (!_pbsUnit.States.Dead) { - _pbsUnit.SendNodeUpdate(PBSEventType.nodeAttacked); + SendAttackAlert(); } } finally @@ -77,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)) From 013c4c2641fe242da8584eede8c5cbb6a4416417 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Sat, 16 Oct 2021 22:50:10 -0700 Subject: [PATCH 318/457] added 8 hour constant to base plant regrowth on --- .../Zones/Terrains/Materials/Plants/PlantHandler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index dd3c8a56e..5c9280b86 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -23,10 +23,13 @@ public class PlantHandler : IPlantHandler private const int AREA_SIZE = 32; // The number of 32x32 cubes across the zone max width of 2048x2048 private const int TIME_SCALING_BASE = 64; + // Number of seconds in 8 hours + private const int EIGHT_HOURS = 28800; + private const int PLANT_REGEN_BASE_DELAY = EIGHT_HOURS / TIME_SCALING_BASE; private readonly IZone _zone; private readonly TimeSpan _natureSleepAmount = TimeSpan.FromSeconds(7); - private IntervalTimer _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(7)); + private IntervalTimer _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(PLANT_REGEN_BASE_DELAY)); private PlantScannerMode _scannerMode = PlantScannerMode.Paused; private bool _zoneFinished; From 2484a52efca76b6d550d55e3dd205bc9f900bc7c Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 17 Oct 2021 18:51:57 -0400 Subject: [PATCH 319/457] a little refactor to support int array (#416) --- .../Zone/ZoneDrawBlockingByDefinition.cs | 2 +- .../ChatCommands/AdminCommandHandlers.cs | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) 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/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 294ffdbee..68744bf82 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -1008,7 +1008,7 @@ public static void ZoneRestoreOriginalGamma(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - Dictionary dictionary = new Dictionary() + var dictionary = new Dictionary() { { "mode", data.Command.Args[0] } }; @@ -1022,17 +1022,29 @@ public static void ZoneDrawBlockingByDefinition(AdminCommandData data) if (!IsDevModeEnabled(data)) return; - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int def); - int[] defs = new int[1]; - defs[0] = def; + int zoneId = data.Sender.ZoneId ?? -1; + int[] defs; + try + { + var 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() + var dictionary = new Dictionary() { - { "definition", defs } + { k.definition, defs } }; - string cmd = string.Format("zoneDrawBlockingByDefinition:zone_{0}:{1}", data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + string cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneDrawBlockingByDefinition.Text, zoneId, GenxyConverter.Serialize(dictionary)); HandleLocalRequest(data, cmd); } [ChatCommand("ZoneAddBlockingToLockedTiles")] From 597fea24d3ce76da766b68b2307786149db8cc4e Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 17 Oct 2021 18:52:19 -0400 Subject: [PATCH 320/457] update dynamicprops too, field term fix (#414) --- .../Zone/Containers/ZoneChangeModule.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs index e1345ca46..8e7757332 100644 --- a/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs +++ b/src/Perpetuum.RequestHandlers/Zone/Containers/ZoneChangeModule.cs @@ -85,6 +85,9 @@ private void EquipChange(IZoneRequest request, Player player, Container containe 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(); From 3dde54be5246516666aa11b9e191ae1e70a5fbc9 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Sun, 17 Oct 2021 17:07:33 -0700 Subject: [PATCH 321/457] fixed logic regarding an 8 hour plant regen timer --- .../Terrains/Materials/Plants/PlantHandler.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index 5c9280b86..00536b484 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -21,15 +21,12 @@ public class PlantHandler : IPlantHandler public delegate IPlantHandler Factory(IZone zone); private const int AREA_SIZE = 32; - // The number of 32x32 cubes across the zone max width of 2048x2048 - private const int TIME_SCALING_BASE = 64; - // Number of seconds in 8 hours - private const int EIGHT_HOURS = 28800; - private const int PLANT_REGEN_BASE_DELAY = EIGHT_HOURS / TIME_SCALING_BASE; + // Number of hours a full pass of plant regen should take + private readonly int FULL_PLANT_REGEN_PASS = 8; private readonly IZone _zone; private readonly TimeSpan _natureSleepAmount = TimeSpan.FromSeconds(7); - private IntervalTimer _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(PLANT_REGEN_BASE_DELAY)); + private IntervalTimer _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds(7)); private PlantScannerMode _scannerMode = PlantScannerMode.Paused; private bool _zoneFinished; @@ -39,6 +36,7 @@ public class PlantHandler : IPlantHandler private int _areaDoneY; private readonly int _areaAmount; private bool _stopSignal; + private int _total_area; public Area WorkArea { private get; set; } @@ -46,8 +44,8 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas - // We want to scale the time it takes to repopulate a zone - AddTimerScaling(_areaAmount); + _total_area = (zone.Size.Width / AREA_SIZE) * (zone.Size.Height / AREA_SIZE); + _plantsTimer = new IntervalTimer(TimeSpan.FromHours(FULL_PLANT_REGEN_PASS / _total_area)); WorkArea = zone.Size.ToArea(); ScannerMode = PlantScannerMode.Scanner; @@ -83,12 +81,6 @@ public void Update(TimeSpan time) }); } - // https://github.com/OpenPerpetuum/PerpetuumServer/issues/304 - private void AddTimerScaling(int zoneWidth) - { - _plantsTimer = new IntervalTimer(TimeSpan.FromSeconds((TIME_SCALING_BASE / zoneWidth) * 7)); - } - private void ProcessPlants() { switch (ScannerMode) From bc19ff86edca4df72f14f73f4649f1a017f87930 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Fri, 22 Oct 2021 16:45:23 -0700 Subject: [PATCH 322/457] github issue #413 - added despawn timer for areabomb --- src/Perpetuum/Zones/Eggs/AreaBomb.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/Eggs/AreaBomb.cs b/src/Perpetuum/Zones/Eggs/AreaBomb.cs index b0eab8162..fca8f8993 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(15); + base.Initialize(); + } protected override void OnSummonSuccess(IZone zone, Player[] summoners) { From cf7703d5ec9c8ccf144177eae2c13eb14b5e2293 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Sat, 23 Oct 2021 12:11:22 -0700 Subject: [PATCH 323/457] cast ints to float to avoid integer rounding --- .../Zones/Terrains/Materials/Plants/PlantHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index 00536b484..962689bd7 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -22,7 +22,7 @@ public class PlantHandler : IPlantHandler private const int AREA_SIZE = 32; // Number of hours a full pass of plant regen should take - private readonly int FULL_PLANT_REGEN_PASS = 8; + private readonly float FULL_PLANT_REGEN_PASS = 8f; private readonly IZone _zone; private readonly TimeSpan _natureSleepAmount = TimeSpan.FromSeconds(7); @@ -36,7 +36,7 @@ public class PlantHandler : IPlantHandler private int _areaDoneY; private readonly int _areaAmount; private bool _stopSignal; - private int _total_area; + private float _total_area; public Area WorkArea { private get; set; } @@ -44,7 +44,7 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas - _total_area = (zone.Size.Width / AREA_SIZE) * (zone.Size.Height / AREA_SIZE); + _total_area = ((float)zone.Size.Width / (float)AREA_SIZE) * ((float)zone.Size.Height / (float)AREA_SIZE); _plantsTimer = new IntervalTimer(TimeSpan.FromHours(FULL_PLANT_REGEN_PASS / _total_area)); WorkArea = zone.Size.ToArea(); From 8b70522f30190aae8c2942ff4e927b7bc486969f Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Tue, 26 Oct 2021 18:50:32 -0700 Subject: [PATCH 324/457] updated plant handler. this is a temp commit --- src/Perpetuum/Zones/Eggs/AreaBomb.cs | 2 +- .../Zones/Terrains/Materials/Plants/PlantHandler.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Zones/Eggs/AreaBomb.cs b/src/Perpetuum/Zones/Eggs/AreaBomb.cs index fca8f8993..fad7c7da8 100644 --- a/src/Perpetuum/Zones/Eggs/AreaBomb.cs +++ b/src/Perpetuum/Zones/Eggs/AreaBomb.cs @@ -21,7 +21,7 @@ public override void AcceptVisitor(IEntityVisitor visitor) } public override void Initialize() { - DespawnTime = TimeSpan.FromMinutes(15); + DespawnTime = TimeSpan.FromMinutes(5); base.Initialize(); } diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index 962689bd7..6389eb1ce 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -22,11 +22,13 @@ public class PlantHandler : IPlantHandler private const int AREA_SIZE = 32; // Number of hours a full pass of plant regen should take - private readonly float FULL_PLANT_REGEN_PASS = 8f; + 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; @@ -44,8 +46,10 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas - _total_area = ((float)zone.Size.Width / (float)AREA_SIZE) * ((float)zone.Size.Height / (float)AREA_SIZE); - _plantsTimer = new IntervalTimer(TimeSpan.FromHours(FULL_PLANT_REGEN_PASS / _total_area)); + _total_area = (zone.Size.Width / (float)AREA_SIZE) * (zone.Size.Height / (float)AREA_SIZE); + var refreshRate = FULL_PLANT_REGEN_PASS / _total_area; + _plantsTimer = new IntervalTimer(TimeSpan.FromHours(refreshRate)); + _natureSleepAmount = TimeSpan.FromHours(refreshRate); WorkArea = zone.Size.ToArea(); ScannerMode = PlantScannerMode.Scanner; From c3096bdc2a8abc4ba6347608fcd1888a4bf7da58 Mon Sep 17 00:00:00 2001 From: Radjax Date: Wed, 27 Oct 2021 18:35:12 -0700 Subject: [PATCH 325/457] Reworked RiftManager initialization in Bootstrapper to account for ZoneRiftConfig existing and MaxRifts > 1. Reworked ZoneRiftConfig to be non-static, added accessor methods, and created Repository for it referenced in RiftManager --- .../PerpetuumBootstrapper.cs | 41 +++++++++-- .../Services/RiftSystem/RiftManager.cs | 8 ++- .../Services/RiftSystem/ZoneRiftConfigs.cs | 72 ++++++++++++++----- 3 files changed, 98 insertions(+), 23 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 139fa34f3..58db08d4e 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2421,6 +2421,23 @@ private void RegisterRifts() { return zone => { + + var zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute(); + if (zoneConfigs.Count < 1) + { + return null; + } + + var record = zoneConfigs[0]; + var maxrifts = record.GetValue("maxrifts"); + + if (maxrifts < 1) + { + return null; + } + if (zone.Configuration.Terraformable) { return new PvpRiftSpawnPositionFinder(zone); @@ -2430,14 +2447,30 @@ private void RegisterRifts() }; }); - _builder.RegisterType(); - _builder.RegisterType(); + _builder.RegisterType().As(); + _builder.RegisterType().As(); _builder.Register>(x => { var ctx = x.Resolve(); - return zone => - { + return zone => { + + var zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") + .SetParameter("@zoneId", zone.Id) + .Execute(); + if (zoneConfigs.Count < 1) + { + return null; + } + + var record = zoneConfigs[0]; + var maxrifts = record.GetValue("maxrifts"); + + if (maxrifts < 1) + { + return null; + } + if (zone is TrainingZone) return null; diff --git a/src/Perpetuum/Services/RiftSystem/RiftManager.cs b/src/Perpetuum/Services/RiftSystem/RiftManager.cs index f3121bbe6..fa2411e8a 100644 --- a/src/Perpetuum/Services/RiftSystem/RiftManager.cs +++ b/src/Perpetuum/Services/RiftSystem/RiftManager.cs @@ -62,23 +62,25 @@ public class RiftManager : IRiftManager 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) { _zone = zone; - _config = ZoneRiftConfigReader.GetForZone(zone); _spawnTime = spawnTime; _spawnPositionFinder = spawnPositionFinder; _entityServices = entityServices; + zoneRiftConfigRepository = new ZoneRiftConfigRepository(zone); + _config = zoneRiftConfigRepository.GetZoneConfig(); } private int _riftCounts; public void Update(TimeSpan time) { - while (_riftCounts < _config.MaxRifts) + while (_riftCounts < _config.GetMaxRifts()) { _nextRiftSpawns.AddLast(new TimeTracker(FastRandom.NextTimeSpan(_spawnTime))); Interlocked.Increment(ref _riftCounts); @@ -100,7 +102,7 @@ private void SpawnRift() var rift = (Rift)_entityServices.Factory.CreateWithRandomEID(DefinitionNames.RIFT); rift.SetDespawnTime(TimeSpan.FromHours(3)); - rift.SetLevel(_config.MaxLevel); + rift.SetLevel(_config.GetMaxLevel()); rift.RemovedFromZone += OnRiftRemovedFromZone; var spawnPosition = _spawnPositionFinder.FindSpawnPosition().ToPosition(); diff --git a/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs b/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs index 61763c487..c00a33c22 100644 --- a/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs +++ b/src/Perpetuum/Services/RiftSystem/ZoneRiftConfigs.cs @@ -1,30 +1,62 @@ 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 static class ZoneRiftConfigReader + 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 static ZoneRiftConfig GetForZone(IZone zone) + public ZoneRiftConfig GetForZone() { var riftConfigs = Db.Query().CommandText("SELECT TOP 1 id, zoneid, maxrifts, maxlevel FROM zoneriftsconfig WHERE zoneid = @zoneId") - .SetParameter("@zoneId", zone.Id) + .SetParameter("@zoneId", _zone.Id) .Execute() - .Select((record) => - { - var maxRifts = record.GetValue("maxrifts"); - var maxLevel = record.GetValue("maxlevel"); - return new ZoneRiftConfig(zone, maxRifts, maxLevel); - }); + .Select(CreateZoneRiftConfigFromRecord); return riftConfigs.FirstOrDefault(); } @@ -35,14 +67,22 @@ public static ZoneRiftConfig GetForZone(IZone zone) /// public class ZoneRiftConfig { - public IZone Zone { get; private set; } - public int MaxRifts { get; private set; } - public int MaxLevel { get; private set; } - public ZoneRiftConfig(IZone zone, int maxRifts, int maxLevel) + private int _zoneid { get; set; } + private int _maxRifts { get; set; } + private int _maxLevel { get; set; } + public ZoneRiftConfig(int zoneid, int maxRifts, int maxLevel) { - Zone = zone; - MaxRifts = maxRifts; - MaxLevel = maxLevel; + _zoneid = zoneid; + _maxRifts = maxRifts; + _maxLevel = maxLevel; + } + + public int GetMaxRifts() { + return _maxRifts; + } + + public int GetMaxLevel() { + return _maxLevel; } } } From 2176eebebb8d4d3e1576aa7970f92eb7e76094da Mon Sep 17 00:00:00 2001 From: Radjax Date: Thu, 28 Oct 2021 18:06:01 -0700 Subject: [PATCH 326/457] Fixing missed reference to IRiftManager, when I updated the other concrete implementations of RiftManager to be interfaces, I missed this one. --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 58db08d4e..a526eecf3 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2481,7 +2481,7 @@ private void RegisterRifts() 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)); + return ctx.Resolve(new TypedParameter(typeof(IZone),zone),new NamedParameter("spawnTime",spawnTime),new NamedParameter("spawnPositionFinder",finder)); }; }); } From bbed3defb3bd894845f696572d95ba53dc62569d Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Thu, 28 Oct 2021 19:54:30 -0700 Subject: [PATCH 327/457] updated code quality per devnull --- .../Zones/Terrains/Materials/Plants/PlantHandler.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index 6389eb1ce..6a7b5ee23 100644 --- a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs +++ b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs @@ -38,7 +38,6 @@ public class PlantHandler : IPlantHandler private int _areaDoneY; private readonly int _areaAmount; private bool _stopSignal; - private float _total_area; public Area WorkArea { private get; set; } @@ -46,10 +45,10 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas - _total_area = (zone.Size.Width / (float)AREA_SIZE) * (zone.Size.Height / (float)AREA_SIZE); - var refreshRate = FULL_PLANT_REGEN_PASS / _total_area; - _plantsTimer = new IntervalTimer(TimeSpan.FromHours(refreshRate)); - _natureSleepAmount = TimeSpan.FromHours(refreshRate); + var _total_area = (zone.Size.Width / (float)AREA_SIZE) * (zone.Size.Height / (float)AREA_SIZE); + var refreshRate = TimeSpan.FromHours(FULL_PLANT_REGEN_PASS / _total_area); + _plantsTimer = new IntervalTimer(refreshRate); + _natureSleepAmount = refreshRate; WorkArea = zone.Size.ToArea(); ScannerMode = PlantScannerMode.Scanner; From 18cabfe82ad674d4be3cb1bf6ef44e807cdf3f15 Mon Sep 17 00:00:00 2001 From: Radjax Date: Thu, 28 Oct 2021 20:52:38 -0700 Subject: [PATCH 328/457] Switching back to concrete types so the bootstrapper knows what its initializing. Added logic specifically for StrongHoldZones since they point to a different table, and moved them ahead of the zoneRiftConfig check as strongholds should be checked first. --- .../PerpetuumBootstrapper.cs | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index a526eecf3..f062277fe 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2447,13 +2447,31 @@ private void RegisterRifts() }; }); - _builder.RegisterType().As(); - _builder.RegisterType().As(); + _builder.RegisterType(); + _builder.RegisterType(); _builder.Register>(x => { var ctx = x.Resolve(); - return zone => { + return zone => + { + if (zone is TrainingZone) + return null; + + if (zone is StrongHoldZone) + { + + var strongHoldExitConfigCount = Db.Query().CommandText("select count(*) from strongholdexitconfig where zoneid = @zoneId;") + .SetParameter("@zoneId", zone.Id) + .ExecuteScalar(); + if (strongHoldExitConfigCount < 1) + { + return null; + } + + return ctx.Resolve(new TypedParameter(typeof(IZone), zone)); + } + var zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") .SetParameter("@zoneId", zone.Id) @@ -2471,17 +2489,9 @@ private void RegisterRifts() return null; } - if (zone is TrainingZone) - return null; - - if (zone is StrongHoldZone) - { - return ctx.Resolve(new TypedParameter(typeof(IZone), zone)); - } - 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)); + return ctx.Resolve(new TypedParameter(typeof(IZone),zone),new NamedParameter("spawnTime",spawnTime),new NamedParameter("spawnPositionFinder",finder)); }; }); } From 8b19835515c3e54a4aae8e6b41679df0a9ba9ba0 Mon Sep 17 00:00:00 2001 From: Radjax Date: Fri, 29 Oct 2021 19:51:21 -0700 Subject: [PATCH 329/457] Removing unnecessary extra code since its covered in a func below. Style change on sql query below to match style elsewhere in code. --- .../PerpetuumBootstrapper.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index f062277fe..97c42a37f 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -2421,23 +2421,6 @@ private void RegisterRifts() { return zone => { - - var zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") - .SetParameter("@zoneId", zone.Id) - .Execute(); - if (zoneConfigs.Count < 1) - { - return null; - } - - var record = zoneConfigs[0]; - var maxrifts = record.GetValue("maxrifts"); - - if (maxrifts < 1) - { - return null; - } - if (zone.Configuration.Terraformable) { return new PvpRiftSpawnPositionFinder(zone); @@ -2461,7 +2444,7 @@ private void RegisterRifts() if (zone is StrongHoldZone) { - var strongHoldExitConfigCount = Db.Query().CommandText("select count(*) from strongholdexitconfig where zoneid = @zoneId;") + var strongHoldExitConfigCount = Db.Query().CommandText("SELECT COUNT(*) FROM strongholdexitconfig WHERE zoneid = @zoneId;") .SetParameter("@zoneId", zone.Id) .ExecuteScalar(); if (strongHoldExitConfigCount < 1) From 092049e7ea6d60966192d1897e559a5deb232b43 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sun, 31 Oct 2021 21:08:35 -0400 Subject: [PATCH 330/457] update link to org project (#421) --- CONTRIBUTING.md | 5 +++-- LICENSE | 2 +- README.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ef339f18..55c6c4b50 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,9 @@ The PerpetuumServer is highly coupled in some instances to DB state. The OPDB h - 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** -## OP-project - Cross Repo issue and feature planning -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 OP-Project issue. When a server change requires a DB change, use the issue too as a mechanism to indicate the coupling. +## 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 diff --git a/LICENSE b/LICENSE index 19fc4b70d..547922963 100644 --- a/LICENSE +++ b/LICENSE @@ -30,7 +30,7 @@ OpenPerpetuum - LICENSE MIT License -Copyright (c) 2018-2020 OpenPerpetuum +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 diff --git a/README.md b/README.md index 67440e5c4..35561ce36 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ If you have any questions about joining the team hit us up on [discord](https:// To see what we are working on acquaint yourself with our: - Website [https://openperpetuum.com](https://openperpetuum.com) - - Project Kanban [Board](https://github.com/OpenPerpetuum/OP-Project) + - Project Kanban [Board](https://github.com/orgs/OpenPerpetuum/projects/2) - [Discord](https://discord.gg/e4gH9Ff) To contribute, join the team and we will get you up and running, or, show us your moxie and submit a pull request! From 9356c22eaeade811fecd9b3d2ee20e2984ca0b16 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Wed, 10 Nov 2021 20:35:20 -0500 Subject: [PATCH 331/457] DynamicPresExtended: spawn location noise (#427) --- src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs index d6a3a3507..669c7bd0f 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/RemoteSpawningFlock.cs @@ -15,7 +15,7 @@ protected override Position GetSpawnPosition(Position spawnOrigin) { if (Presence is DynamicPresenceExtended presence) { - return presence.SpawnLocation.Clamp(Presence.Zone.Size); + spawnOrigin = presence.SpawnLocation.Clamp(Presence.Zone.Size); } return base.GetSpawnPosition(spawnOrigin); } @@ -24,7 +24,7 @@ protected override Position GetHomePosition(Position spawnOrigin) { if (Presence is DynamicPresenceExtended presence) { - return presence.DynamicPosition.Clamp(Presence.Zone.Size); + spawnOrigin = presence.DynamicPosition.Clamp(Presence.Zone.Size); } return base.GetSpawnPosition(spawnOrigin); } From a5b549f9388a1fc50266fc550dd08c0e073dabf3 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 11 Nov 2021 22:53:44 -0500 Subject: [PATCH 332/457] armor rep aggro reduce, boss wave spawn-in range increase (#431) --- src/Perpetuum/Modules/ArmorRepairModule.cs | 6 +++--- .../NpcSpawnEventHandlers/NpcReinforcementSpawner.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Modules/ArmorRepairModule.cs b/src/Perpetuum/Modules/ArmorRepairModule.cs index 05d67bb57..dbeb02b57 100644 --- a/src/Perpetuum/Modules/ArmorRepairModule.cs +++ b/src/Perpetuum/Modules/ArmorRepairModule.cs @@ -65,9 +65,9 @@ public override void AcceptVisitor(IEntityVisitor 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/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs index c9aef10ff..d3078dfae 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -16,7 +16,7 @@ public class NpcReinforcementSpawner : NpcSpawnEventHandler EventType.NpcReinforce; From b58c4f226c4526a52db327cb0ab41ced59ab5458 Mon Sep 17 00:00:00 2001 From: Junelicious Date: Tue, 16 Nov 2021 03:09:27 +0300 Subject: [PATCH 333/457] added target painter as a valid module for entityVisitor (#437) --- .../EffectModules/TargetPainterModule.cs | 9 ++++++++- src/Perpetuum/Zones/NpcSystem/Turret.cs | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs b/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs index bf219b96c..be95b5d81 100644 --- a/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs @@ -1,4 +1,5 @@ -using Perpetuum.ExportedTypes; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; using Perpetuum.Items; using Perpetuum.Zones.Effects; @@ -14,6 +15,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/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index aeedee438..0bb57a2d4 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -29,7 +29,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private readonly IntervalTimer _timer; private readonly ActiveModule _module; @@ -169,6 +170,21 @@ public void Visit(WebberModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } + private const double PAINTER_CORE_THRESHOLD = 0.65; + + public void Visit(TargetPainterModule module) + { + if (module.ParentRobot.CorePercentage < PAINTER_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 BLOBBER_CORE_THRESHOLD = 0.55; public void Visit(BlobEmissionModulatorModule module) From a24c2cd08eb5604f80a9e9553987b8fcfe29ea53 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Mon, 15 Nov 2021 17:04:08 -0800 Subject: [PATCH 334/457] issue 419 fix --- src/Perpetuum/Accounting/AccountManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 149cafcf6..4ca8ef94a 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -106,7 +106,7 @@ public int GetLockedEpByAccount(Account account) } private const double BOOSTMULTIPLIERMAX = 25; - private const double SERVER_DESIRED_EP_LEVEL = 750000; + private const double SERVER_DESIRED_EP_LEVEL = 1000000; private const double GAURANTEED_BOOST_MAX_THRESH = 45000; public IDictionary GetEPData(Account account,Character character) From 5aff725931ad5327557745b168de483c2c3ebac5 Mon Sep 17 00:00:00 2001 From: Junelicious Date: Wed, 17 Nov 2021 21:01:42 +0300 Subject: [PATCH 335/457] Added InsertVendorSellOrder to insert gamma sell orders on base construction --- src/Perpetuum/Services/MarketEngine/Market.cs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/MarketEngine/Market.cs b/src/Perpetuum/Services/MarketEngine/Market.cs index 75faf6b41..b23965ffc 100644 --- a/src/Perpetuum/Services/MarketEngine/Market.cs +++ b/src/Perpetuum/Services/MarketEngine/Market.cs @@ -796,13 +796,45 @@ public void InsertVendorBuyOrder(int definition, double price, long vendorEid = .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); } + public void InsertVendorSellOrder(int definition, double price, long vendorEid = 0) + { + 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); + } + public void AddOtherStuffToGammaMarket() { - //5373 5375 + //5373 5375 5 //def_ammo_mining_probe_energymineral_direction //def_ammo_mining_probe_energymineral_tile + //def_noobbot InsertVendorBuyOrder(5373,45); InsertVendorBuyOrder(5375,45); + InsertVendorBuyOrder(5, 100); + InsertVendorSellOrder(5, 10000); //5376 def_gate_capsule InsertVendorBuyOrder(5376, 250000); From 1188220b04ff4d8c4c0a130e2a68265d701c3c79 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Thu, 18 Nov 2021 02:21:47 -0500 Subject: [PATCH 336/457] Npc aggro refactor and enhancement (#434) * refactor, add primary target selection logic to base combat ai * missing file from proj * fix bug on constant target switching * probability tweak --- src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Zones/NpcSystem/Npc.cs | 260 +++++++----------- .../Zones/NpcSystem/PrimaryTargetStrategy.cs | 110 ++++++++ 3 files changed, 211 insertions(+), 160 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 1326fab2c..38a9c86aa 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -725,6 +725,7 @@ + diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index f4a7c27ae..dd34fbf46 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -152,55 +152,88 @@ protected override void ToAggressorAI() public class StationaryCombatAI : CombatAI { - private enum PrimaryLockStrategy + private readonly IntervalTimer _updateFrequency = new IntervalTimer(650); + public StationaryCombatAI(Npc npc) : base(npc) { } + + protected override PrimaryLockSelectionStrategySelector InitSelector() { - Random, - Hostile, - Closest, - OptimalRange + return PrimaryLockSelectionStrategySelector.Create() + .WithStrategy(PrimaryLockStrategy.Hostile, 1) + .WithStrategy(PrimaryLockStrategy.Closest, 2) + .WithStrategy(PrimaryLockStrategy.OptimalRange, 3) + .WithStrategy(PrimaryLockStrategy.Random, 10) + .Build(); } - private class PrimaryLockSelectionStrategySelector + public override void Update(TimeSpan time) { - private readonly WeightedCollection _selection; - public PrimaryLockSelectionStrategySelector() - { - _selection = new WeightedCollection(); - _selection.Add(PrimaryLockStrategy.Hostile, 1); - _selection.Add(PrimaryLockStrategy.Closest, 2); - _selection.Add(PrimaryLockStrategy.OptimalRange, 3); - _selection.Add(PrimaryLockStrategy.Random, 10); - } - public PrimaryLockStrategy GetStrategy() + FindHostiles(time); + base.Update(time); + } + + private void FindHostiles(TimeSpan time) + { + _updateFrequency.Update(time); + if (_updateFrequency.Passed) { - return _selection.GetRandom(); + _updateFrequency.Reset(); + npc.LookingForHostiles(); } } - private readonly IntervalTimer _updateFrequency = new IntervalTimer(500); - private readonly IntervalTimer _primarySelectTimer = new IntervalTimer(0); - private readonly PrimaryLockSelectionStrategySelector _stratSelector = new PrimaryLockSelectionStrategySelector(); - public StationaryCombatAI(Npc npc) : base(npc) { } + protected override TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + } + } + + public class CombatAI : NpcAI + { + private List _moduleActivators; + protected bool _npcHasMissiles = false; + private const int UPDATE_FREQ = 1650; + private TimeSpan _hostilesUpdateFrequency = TimeSpan.FromMilliseconds(UPDATE_FREQ); + private readonly IntervalTimer _processHostilesTimer = new IntervalTimer(UPDATE_FREQ); + private readonly IntervalTimer _primarySelectTimer = new IntervalTimer(UPDATE_FREQ); + private PrimaryLockSelectionStrategySelector _stratSelector; + public CombatAI(Npc npc) : base(npc) { } + + protected virtual PrimaryLockSelectionStrategySelector InitSelector() + { + return PrimaryLockSelectionStrategySelector.Create() + .WithStrategy(PrimaryLockStrategy.Hostile, 9) + .WithStrategy(PrimaryLockStrategy.Random, 1) + .Build(); + } + + public override void Enter() + { + _stratSelector = InitSelector(); + _moduleActivators = npc.ActiveModules.Select(m => new ModuleActivator(m)).ToList(); + _npcHasMissiles = npc.ActiveModules.OfType().Any(); + _processHostilesTimer.Update(_hostilesUpdateFrequency); + _primarySelectTimer.Update(_hostilesUpdateFrequency); + base.Enter(); + } public override void Update(TimeSpan time) { - FindHostiles(time); UpdateHostiles(time); UpdatePrimaryTarget(time); RunModules(time); } - private void FindHostiles(TimeSpan time) + protected void UpdateHostiles(TimeSpan time) { - _updateFrequency.Update(time); - if (_updateFrequency.Passed) + _processHostilesTimer.Update(time); + if (_processHostilesTimer.Passed) { - _updateFrequency.Reset(); - npc.LookingForHostiles(); + _processHostilesTimer.Reset(); + ProcessHostiles(); } } - private void UpdatePrimaryTarget(TimeSpan time) + protected void UpdatePrimaryTarget(TimeSpan time) { _primarySelectTimer.Update(time); if (_primarySelectTimer.Passed) @@ -210,9 +243,17 @@ private void UpdatePrimaryTarget(TimeSpan time) } } + protected void RunModules(TimeSpan time) + { + foreach (var activator in _moduleActivators) + { + activator.Update(time); + } + } + private UnitLock[] GetValidLocks() { - return npc.GetLocks().Select(l => (UnitLock)l).Where(u => IsLockValidTarget(u) && !u.Primary).ToArray(); + return npc.GetLocks().Select(l => (UnitLock)l).Where(u => IsLockValidTarget(u)).ToArray(); } private bool SelectPrimaryTarget() @@ -221,40 +262,7 @@ private bool SelectPrimaryTarget() if (validLocks.Length < 1) return false; - var strategy = _stratSelector.GetStrategy(); - switch (strategy) - { - case PrimaryLockStrategy.Hostile: - PrimaryMostHated(validLocks); - break; - case PrimaryLockStrategy.Closest: - PrimaryClosest(validLocks); - break; - case PrimaryLockStrategy.OptimalRange: - PrimaryRandomWithinOptimal(validLocks); - break; - case PrimaryLockStrategy.Random: - default: - PrimaryRandom(validLocks); - break; - } - return validLocks.Any(l => l.Primary); - } - - protected override void SetLockForHostile(Hostile hostile) - { - if (npc.GetPrimaryLock() == null) - { - base.SetLockForHostile(hostile); - return; - } - - var l = npc.GetLockByUnit(hostile.unit); - if (l == null) - { - if (TryMakeFreeLockSlotFor(hostile)) - npc.AddLock(hostile.unit, false); - } + return _stratSelector?.TryUseStrategy(npc, validLocks) ?? false; } private bool IsLockValidTarget(UnitLock unitLock) @@ -266,7 +274,7 @@ private bool IsLockValidTarget(UnitLock unitLock) if (visibility == null) return false; - var r = visibility.GetLineOfSight(false); + var r = visibility.GetLineOfSight(_npcHasMissiles); if (r != null) { if (r.hit && (r.blockingFlags & BlockingFlags.Plant) == 0) @@ -275,34 +283,16 @@ private bool IsLockValidTarget(UnitLock unitLock) return unitLock.Target.GetDistance(npc) < npc.MaxCombatRange; } - private bool PrimaryMostHated(UnitLock[] locks) - { - var hostiles = npc.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(mostHostileLock); - } - - private bool PrimaryRandomWithinOptimal(UnitLock[] locks) - { - return TrySetPrimaryLock(locks.Where(k => k.Target.GetDistance(npc) < npc.BestCombatRange).RandomElement()); - } - - private bool PrimaryClosest(UnitLock[] locks) + protected virtual TimeSpan SetPrimaryDwellTime() { - return TrySetPrimaryLock(locks.OrderBy(u => u.Target.GetDistance(npc)).First()); + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10)); } - private bool PrimaryRandom(UnitLock[] locks) - { - return TrySetPrimaryLock(locks.RandomElement()); - } - - private void SetPrimaryUpdateDelay(bool newPrimary) + protected virtual void SetPrimaryUpdateDelay(bool newPrimary) { if (newPrimary) { - _primarySelectTimer.Interval = FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + _primarySelectTimer.Interval = SetPrimaryDwellTime(); } else if (GetValidLocks().Length > 0) { @@ -318,51 +308,6 @@ private void SetPrimaryUpdateDelay(bool newPrimary) } } - private bool TrySetPrimaryLock(Lock l) - { - if (l == null) return false; - npc.SetPrimaryLock(l); - return true; - } - } - - public class CombatAI : NpcAI - { - private List _moduleActivators; - private readonly IntervalTimer _processHostilesTimer = new IntervalTimer(1500); - - public CombatAI(Npc npc) : base(npc) { } - - public override void Enter() - { - _moduleActivators = npc.ActiveModules.Select(m => new ModuleActivator(m)).ToList(); - base.Enter(); - } - - protected void UpdateHostiles(TimeSpan time) - { - _processHostilesTimer.Update(time); - if (_processHostilesTimer.Passed) - { - _processHostilesTimer.Reset(); - ProcessHostiles(); - } - } - - protected void RunModules(TimeSpan time) - { - foreach (var activator in _moduleActivators) - { - activator.Update(time); - } - } - - public override void Update(TimeSpan time) - { - UpdateHostiles(time); - RunModules(time); - } - protected bool IsAttackable(Hostile hostile) { if (!hostile.unit.InZone) @@ -393,9 +338,9 @@ protected bool IsAttackable(Hostile hostile) return true; } - protected virtual void SetLockForHostile(Hostile hostile) + private void SetLockForHostile(Hostile hostile) { - var mostHated = npc.ThreatManager.GetMostHatedHostile() == hostile; + var mostHated = GetPrimaryOrMostHatedHostile() == hostile; var l = npc.GetLockByUnit(hostile.unit); if (l == null) @@ -442,6 +387,14 @@ protected bool TryMakeFreeLockSlotFor(Hostile hostile) weakestLock.Cancel(); return true; } + + protected Hostile GetPrimaryOrMostHatedHostile() + { + var primaryHostile = npc.ThreatManager.Hostiles.Where(h => h.unit == (npc.GetPrimaryLock() as UnitLock)?.Target).FirstOrDefault(); + if (primaryHostile != null) + return primaryHostile; + return npc.ThreatManager.GetMostHatedHostile(); + } } public class HomingAI : CombatAI @@ -477,9 +430,9 @@ public override void Enter() _movement = new PathMovement(path); _movement.Start(npc); + npc.BossInfo?.OnDeAggro(); }); - npc.BossInfo?.OnDeAggro(); base.Enter(); } @@ -499,27 +452,15 @@ public override void Update(TimeSpan time) base.Update(time); } - protected override void ToHomeAI() - { - // mar haza megy - } - - protected override void ToAggressorAI() - { - - } + protected override void ToHomeAI() { } + protected override void ToAggressorAI() { } } public class AggressorAI : CombatAI { - public AggressorAI(Npc npc) : base(npc) - { - } + public AggressorAI(Npc npc) : base(npc){ } - protected override void ToAggressorAI() - { - // mar combatban van - } + protected override void ToAggressorAI(){ } public override void Exit() { @@ -560,7 +501,7 @@ private void EnterEvadeMode() private void UpdateHostile(TimeSpan time) { - var mostHated = npc.ThreatManager.GetMostHatedHostile(); + var mostHated = GetPrimaryOrMostHatedHostile(); if (mostHated == null) return; @@ -579,7 +520,7 @@ private void UpdateHostile(TimeSpan time) var visibility = npc.GetVisibility(mostHated.unit); if (visibility != null) { - var r = visibility.GetLineOfSight(false); + var r = visibility.GetLineOfSight(_npcHasMissiles); if (r.hit) findNewTargetPosition = true; } @@ -739,11 +680,6 @@ protected NpcBehavior(NpcBehaviorType type) Type = type; } - public void Update(TimeSpan time) - { - - } - public static NpcBehavior Create(NpcBehaviorType type) { switch (type) @@ -894,9 +830,15 @@ public void AddPseudoThreat(Unit hostile) _pseudoThreatManager.AddOrRefreshExisting(hostile); } + private readonly IntervalTimer _psuedoUpdateFreq = new IntervalTimer(TimeSpan.FromMilliseconds(650)); private void UpdatePseudoThreats(TimeSpan time) { - _pseudoThreatManager.Update(time); + _psuedoUpdateFreq.Update(time); + if (_psuedoUpdateFreq.Passed) + { + _pseudoThreatManager.Update(_psuedoUpdateFreq.Elapsed); + _psuedoUpdateFreq.Reset(); + } } private void RemovePseudoThreat(Unit hostile) @@ -1127,8 +1069,6 @@ protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); - Behavior.Update(time); - AI.Update(time); UpdatePseudoThreats(time); diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs new file mode 100644 index 000000000..981088237 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs @@ -0,0 +1,110 @@ +using Perpetuum.Collections; +using Perpetuum.Zones.Locking.Locks; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem +{ + public enum PrimaryLockStrategy + { + Random, + Hostile, + Closest, + OptimalRange + } + + public delegate bool TargetSelectionStrategy(Npc npc, UnitLock[] locks); + + public static class Strategies + { + public static Dictionary All = new Dictionary() + { + { PrimaryLockStrategy.Random, TargetRandom }, + { PrimaryLockStrategy.Hostile, TargetMostHated }, + { PrimaryLockStrategy.Closest, TargetClosest }, + { PrimaryLockStrategy.OptimalRange, TargetWithinOptimal } + }; + + public static TargetSelectionStrategy GetStrategy(PrimaryLockStrategy strategyType) + { + return All.GetOrDefault(strategyType); + } + + public static bool TryInvokeStrategy(PrimaryLockStrategy strategyType, Npc npc, UnitLock[] locks) + { + var strat = GetStrategy(strategyType); + if (strat == null) + return false; + return strat(npc, locks); + } + + private static bool TargetMostHated(Npc npc, UnitLock[] locks) + { + var hostiles = npc.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(npc, mostHostileLock); + } + + private static bool TargetWithinOptimal(Npc npc, UnitLock[] locks) + { + return TrySetPrimaryLock(npc, locks.Where(k => k.Target.GetDistance(npc) < npc.BestCombatRange).RandomElement()); + } + + private static bool TargetClosest(Npc npc, UnitLock[] locks) + { + return TrySetPrimaryLock(npc, locks.OrderBy(u => u.Target.GetDistance(npc)).First()); + } + + private static bool TargetRandom(Npc npc, UnitLock[] locks) + { + return TrySetPrimaryLock(npc, locks.RandomElement()); + } + + private static bool TrySetPrimaryLock(Npc npc, Lock l) + { + if (l == null) return false; + npc.SetPrimaryLock(l); + return true; + } + } + + + + public class PrimaryLockSelectionStrategySelector + { + private readonly WeightedCollection _selection; + public PrimaryLockSelectionStrategySelector(WeightedCollection selection) + { + _selection = selection; + } + public bool TryUseStrategy(Npc npc, UnitLock[] locks) + { + var stratType = _selection.GetRandom(); + return Strategies.TryInvokeStrategy(stratType, npc, locks); + } + public static PrimaryLockSelectionStrategyBuilder Create() + { + return new PrimaryLockSelectionStrategyBuilder(); + } + + public class PrimaryLockSelectionStrategyBuilder + { + private readonly WeightedCollection _selection; + public PrimaryLockSelectionStrategyBuilder() + { + _selection = new WeightedCollection(); + } + public PrimaryLockSelectionStrategyBuilder WithStrategy(PrimaryLockStrategy strategy, int weight = 1) + { + _selection.Add(strategy, weight); + return this; + } + + public PrimaryLockSelectionStrategySelector Build() + { + return new PrimaryLockSelectionStrategySelector(_selection); + } + } + } +} From 288af29a608250ce26eae5eb6be02b97574e619a Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 19 Nov 2021 23:36:30 -0500 Subject: [PATCH 337/457] prevent wall/gate usage with staging terminals (#442) --- src/Perpetuum/Zones/PBS/PBSHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index 9bc155f7b..b5a99d55f 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -1597,7 +1597,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; From 4c8fe02a9d1f492b6742ff5a4212093c25d10c6b Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Fri, 19 Nov 2021 23:36:48 -0500 Subject: [PATCH 338/457] Staging term expire time persistence (#443) * persist expiration time to dynamic props to write to db on shutdown, and read in on startup * fixes for reinforce cycle not killing base, no need to cache elapsed time --- .../DockingBases/ExpiringPBSDockingBase.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs index 1643c2bec..eb3ea394b 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -14,8 +14,8 @@ namespace Perpetuum.Zones.PBS.DockingBases public class ExpiringPBSDockingBase : PBSDockingBase { private IUnitDespawnHelper _despawnHelper; - private TimeSpan _aliveElapsed; private readonly TimeSpan _alertPeriod = TimeSpan.FromHours(1); + private readonly TimeSpan _minLife = TimeSpan.FromMinutes(5); private const int MIN_LIFE_HOURS = 72; private bool _alerted; @@ -46,31 +46,49 @@ public TimeSpan LifeTime } } + public DateTime EndTime + { + get + { + return DynamicProperties.GetOrAdd(k.endTime, () => DateTime.Now + LifeTime); + } + } + + public TimeSpan Remaining + { + get + { + return (EndTime - DateTime.Now).Max(_minLife); + } + } + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { - _despawnHelper = UnitDespawnHelper.Create(this, LifeTime); - _despawnHelper.DespawnStrategy = Kill; + _despawnHelper = UnitDespawnHelper.Create(this, Remaining); + _despawnHelper.DespawnStrategy = (unit) => + { + ReinforceHandler.ReinforceCounter = 0; + Kill(); + }; base.OnEnterZone(zone, enterType); } protected override void OnUpdate(TimeSpan time) { - if (!_alerted) - WarnIfAboutToExpire(time); + WarnIfAboutToExpire(); _despawnHelper?.Update(time, this); base.OnUpdate(time); } - private void WarnIfAboutToExpire(TimeSpan time) + private void WarnIfAboutToExpire() { - _aliveElapsed += time; - if (LifeTime - _aliveElapsed < _alertPeriod) + if (!_alerted && Remaining < _alertPeriod) { - Task.Run(() => _pbsObjectHelper.SendAttackAlert()); _alerted = true; + Task.Run(() => _pbsObjectHelper.SendAttackAlert()); } } From 79e756e877f80d742bd09706e262b1d664dd04f8 Mon Sep 17 00:00:00 2001 From: Junelicious Date: Sun, 21 Nov 2021 04:10:25 +0300 Subject: [PATCH 339/457] Replaced gamma terminal inserted orders added sell for geo charges, artifact charges, field container, syn-sec container, decon block and concrete-layer. --- src/Perpetuum/Services/MarketEngine/Market.cs | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/Perpetuum/Services/MarketEngine/Market.cs b/src/Perpetuum/Services/MarketEngine/Market.cs index b23965ffc..943134fb5 100644 --- a/src/Perpetuum/Services/MarketEngine/Market.cs +++ b/src/Perpetuum/Services/MarketEngine/Market.cs @@ -827,17 +827,46 @@ public void InsertVendorSellOrder(int definition, double price, long vendorEid = public void AddOtherStuffToGammaMarket() { - //5373 5375 5 - //def_ammo_mining_probe_energymineral_direction - //def_ammo_mining_probe_energymineral_tile - //def_noobbot - InsertVendorBuyOrder(5373,45); - InsertVendorBuyOrder(5375,45); - InsertVendorBuyOrder(5, 100); - InsertVendorSellOrder(5, 10000); - - //5376 def_gate_capsule - InsertVendorBuyOrder(5376, 250000); + //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() From 617c00d44c93e95e94fd6d492350a055cd3ea1e0 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 22 Nov 2021 00:30:09 -0500 Subject: [PATCH 340/457] NPC fix: aggro debounce on lockchange, tilechange, and homepos fix (#445) --- src/Perpetuum/Zones/NpcSystem/Npc.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index dd34fbf46..d98f913e0 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -405,13 +405,17 @@ public class HomingAI : CombatAI public HomingAI(Npc npc) : base(npc) { - _maxReturnHomeRadius = (npc.HomeRange * 0.4).Clamp(1, 20); + _maxReturnHomeRadius = (npc.HomeRange * 0.4).Clamp(3, 20); _pathFinder = new AStarFinder(Heuristic.Manhattan, npc.IsWalkable); } public override void Enter() { - var randomHome = npc.HomePosition.GetRandomPositionInRange2D(1, _maxReturnHomeRadius); + var randomHome = npc.Zone.FindPassablePointInRadius(npc.HomePosition, (int)_maxReturnHomeRadius); + if(randomHome == default) + { + randomHome = npc.HomePosition; + } _pathFinder.FindPathAsync(npc.CurrentPosition, randomHome).ContinueWith(t => { var path = t.Result; @@ -1134,25 +1138,35 @@ private bool IsInAggroRange(Unit target) return IsStationary || IsInRangeOf3D(target, AGGRO_RANGE); } + private readonly TimeKeeper _debounceLockChange = new TimeKeeper(TimeSpan.FromSeconds(2.5)); protected override void OnUnitLockStateChanged(Lock @lock) { + if (!_debounceLockChange.Expired) + return; + var unitLock = @lock as UnitLock; - if ( unitLock == null ) + if (unitLock == null) return; if (unitLock.Target != this) return; - if (unitLock.State != LockState.Locked) + 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); + _debounceLockChange.Reset(); } + private readonly TimeKeeper _debounceBodyPull = new TimeKeeper(TimeSpan.FromSeconds(2.5)); protected override void OnUnitTileChanged(Unit target) { + if (!_debounceBodyPull.Expired) + return; + AddBodyPullThreat(target); + _debounceBodyPull.Reset(); } internal override bool IsHostile(Player player) From 0efe80a78a526d136cce65568edb1c3af0551b57 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 22 Nov 2021 00:30:31 -0500 Subject: [PATCH 341/457] PBS base limit check fixes (#449) --- src/Perpetuum/Zones/PBS/PBSHelper.cs | 35 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/PBSHelper.cs b/src/Perpetuum/Zones/PBS/PBSHelper.cs index b5a99d55f..a9c3cbe1b 100644 --- a/src/Perpetuum/Zones/PBS/PBSHelper.cs +++ b/src/Perpetuum/Zones/PBS/PBSHelper.cs @@ -115,13 +115,13 @@ public static int GetCapsuleDefinitionByPBSObjectDefinition(int nodeDefinition) public static ErrorCodes ValidateExpiringPBSDockingBasePlacement(IZone zone, Position position, long owner, EntityDefault definition) { - var expiringBases = zone.Units.Where(u => u is ExpiringPBSDockingBase); + var expiringBasesAndEggs = zone.Units.Where(IsExpiringBaseOrEgg); - if (expiringBases.Count() >= MAX_EXPIRING_BASES_PER_ZONE) + if (expiringBasesAndEggs.Count() >= MAX_EXPIRING_BASES_PER_ZONE) { return ErrorCodes.MaxExpiringBasePerZoneReached; } - else if (expiringBases.Count(u => u.Owner == owner) >= MAX_EXPIRING_BASES_PER_ZONE_PER_CORP) + else if (expiringBasesAndEggs.Count(u => IsUnitOrEggOwnedByCorp(u, owner)) >= MAX_EXPIRING_BASES_PER_ZONE_PER_CORP) { return ErrorCodes.MaxExpiringBasePerZonePerCorpReached; } @@ -135,11 +135,10 @@ public static ErrorCodes ValidateExpiringPBSDockingBasePlacement(IZone zone, Pos public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position position, long owner, EntityDefault definition) { - //Get PBS docking bases - excluding Expiring types - var bases = zone.Units.Where(u => u is PBSDockingBase && !(u is ExpiringPBSDockingBase)); + //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; @@ -147,11 +146,10 @@ public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position po { return ErrorCodes.MaxDockingBasePerZoneReached; } - - if (maxBasesPerZone > MAX_BASES_PER_CORP_PER_ZONE) + else if (maxBasesPerZone > MAX_BASES_PER_CORP_PER_ZONE) { //only a set number of bases for one corporation - if (bases.Count(u => u.Owner == owner) >= MAX_BASES_PER_CORP_PER_ZONE) + if (bases.Count(u => IsUnitOrEggOwnedByCorp(u, owner)) >= MAX_BASES_PER_CORP_PER_ZONE) { return ErrorCodes.MaxDockingBasePerZonePerCorporationReached; } @@ -160,6 +158,25 @@ public static ErrorCodes ValidatePBSDockingBasePlacement(IZone zone, Position po 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; From 6435e5b142e7fda5d5aab653b89eae08c199e08e Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 22 Nov 2021 00:30:56 -0500 Subject: [PATCH 342/457] ExpiringPBSBase while in reinforce fix (#451) --- src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs index eb3ea394b..8c526e86d 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -68,6 +68,7 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) _despawnHelper.DespawnStrategy = (unit) => { ReinforceHandler.ReinforceCounter = 0; + ReinforceHandler.CurrentState.ToVulnerable(); Kill(); }; From fb33d05fb7e11838111c1014f3fc130747198621 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Mon, 22 Nov 2021 01:23:37 -0500 Subject: [PATCH 343/457] ExpiringBase: disallow deconstruction state change command (#452) --- .../Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs | 9 ++++++++- src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs index 8c526e86d..f7f70f46f 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -1,4 +1,5 @@ -using Perpetuum.Common; +using Perpetuum.Accounting.Characters; +using Perpetuum.Common; using Perpetuum.Groups.Corporations; using Perpetuum.Items.Templates; using Perpetuum.Services.Channels; @@ -97,5 +98,11 @@ public override ErrorCodes IsDeconstructAllowed() { return ErrorCodes.DockingBaseNotSetToDeconstruct; } + + public override ErrorCodes SetDeconstructionRight(Character issuer, bool state) + { + DynamicProperties.Remove(k.allowDeconstruction); + return ErrorCodes.DockingBaseNotSetToDeconstruct; + } } } diff --git a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs index ddb9e3775..bad6e7b24 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/PBSDockingBase.cs @@ -336,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); From 5076b5c3007fdfb04a41f1146bc3bf95e6767915 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Mon, 22 Nov 2021 13:12:04 -0800 Subject: [PATCH 344/457] added exception handler per issue 426 --- .../EventServices/EventProcessors/NpcStateAnnouncer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs index f9dbf56dc..10f111703 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -51,7 +51,14 @@ private int GetNpcDef(NpcStateMessage msg) private string GetNpcName(int def) { var nameToken = EntityDefault.Get(def).Name + "_name"; - var name = _nameDictionary[nameToken]?.ToString(); + var name = ""; + try + { + name = _nameDictionary[nameToken]?.ToString(); + } catch (KeyNotFoundException) + { + name = 'NameNotFound'; + } return name ?? string.Empty; } From 1e46a375893961f34db8ebce5c6358ec0f0fcb2f Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Wed, 24 Nov 2021 17:06:51 -0800 Subject: [PATCH 345/457] added exception handler per 426 --- .../EventServices/EventProcessors/NpcStateAnnouncer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs index 10f111703..54e08685c 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -3,6 +3,7 @@ using Perpetuum.Services.Channels; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Log.Loggers; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -55,9 +56,10 @@ private string GetNpcName(int def) try { name = _nameDictionary[nameToken]?.ToString(); - } catch (KeyNotFoundException) + } + catch (KeyNotFoundException) { - name = 'NameNotFound'; + name = "NameNotFound"; } return name ?? string.Empty; } From 2ff38efb069da530a1d3f47d48ce452023dc8813 Mon Sep 17 00:00:00 2001 From: "[object Object]" <2634337+MikeJeffers@users.noreply.github.com> Date: Sat, 27 Nov 2021 14:52:59 -0500 Subject: [PATCH 346/457] terminal chat message on join and MOTD, mails to owners (#453) --- .../Units/DockingBases/DockingBase.cs | 2 +- .../DockingBases/ExpiringPBSDockingBase.cs | 86 ++++++++++++++++--- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/Perpetuum/Units/DockingBases/DockingBase.cs b/src/Perpetuum/Units/DockingBases/DockingBase.cs index 7522d3012..8a340a5a0 100644 --- a/src/Perpetuum/Units/DockingBases/DockingBase.cs +++ b/src/Perpetuum/Units/DockingBases/DockingBase.cs @@ -250,7 +250,7 @@ public Robot CreateStarterRobotForCharacter(Character character,bool setActive = protected virtual bool CanCreateEquippedStartRobot => Zone?.Configuration.Protected ?? false; - public void JoinChannel(Character character) + public virtual void JoinChannel(Character character) { ChannelManager.JoinChannel(ChannelName,character,ChannelMemberRole.Undefined,null); } diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs index f7f70f46f..4d46802a9 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -1,24 +1,34 @@ 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 _alertPeriod = TimeSpan.FromHours(1); - private readonly TimeSpan _minLife = TimeSpan.FromMinutes(5); - private const int MIN_LIFE_HOURS = 72; - private bool _alerted; + 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, @@ -37,13 +47,14 @@ public ExpiringPBSDockingBase( sparkTeleportHelper, pbsObjectHelperFactory) { + _announcer = Character.GetByNick(SENDER_CHARACTER_NICKNAME); } public TimeSpan LifeTime { get { - return TimeSpan.FromHours(ED.Config.lifeTime ?? MIN_LIFE_HOURS); + return TimeSpan.FromHours(ED.Config.lifeTime ?? 72); } } @@ -59,7 +70,7 @@ public TimeSpan Remaining { get { - return (EndTime - DateTime.Now).Max(_minLife); + return (EndTime - DateTime.Now).Max(_minLifeOnInit); } } @@ -72,26 +83,33 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) 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) { - WarnIfAboutToExpire(); + if (_firstUpdate) + OnFirst(); _despawnHelper?.Update(time, this); base.OnUpdate(time); } - private void WarnIfAboutToExpire() + public override void JoinChannel(Character character) { - if (!_alerted && Remaining < _alertPeriod) - { - _alerted = true; - Task.Run(() => _pbsObjectHelper.SendAttackAlert()); - } + base.JoinChannel(character); + ChannelManager.Announcement(ChannelName, _announcer, $"Base is going to expire in: {Remaining.ToHumanTimeString()}"); } public override ErrorCodes IsDeconstructAllowed() @@ -104,5 +122,47 @@ 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."; + } } } From 404c2623fc96ba81458ccf5431d06119a4b736d7 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Sun, 28 Nov 2021 13:32:20 -0800 Subject: [PATCH 347/457] Fix for 426 --- .gitignore | 3 ++- .../EventServices/EventProcessors/NpcStateAnnouncer.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0cd65a833..6740e4f0a 100644 --- a/.gitignore +++ b/.gitignore @@ -257,4 +257,5 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc +/PerpetuumTests diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs index 54e08685c..ab0e6715b 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -61,6 +61,7 @@ private string GetNpcName(int def) { name = "NameNotFound"; } + //var name = _nameDictionary[nameToken]?.ToString(); return name ?? string.Empty; } From 7a0b8c63abcf665d09300a46e0a13c16aea7424d Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Wed, 1 Dec 2021 13:44:23 -0800 Subject: [PATCH 348/457] Added logging method, modified exception handling for 426 --- .../EventProcessors/NpcStateAnnouncer.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs index ab0e6715b..eff5f87dd 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -3,9 +3,9 @@ using Perpetuum.Services.Channels; using Perpetuum.Services.EventServices.EventMessages; using Perpetuum.Zones.NpcSystem.Flocks; -using Perpetuum.Log.Loggers; using System.Collections.Generic; using System.Text.RegularExpressions; +using Perpetuum.Log; namespace Perpetuum.Services.EventServices.EventProcessors { @@ -53,18 +53,24 @@ private string GetNpcName(int def) { var nameToken = EntityDefault.Get(def).Name + "_name"; var name = ""; - try + if (_nameDictionary.TryGetValue(nameToken, out name) != true) { - name = _nameDictionary[nameToken]?.ToString(); + WriteNPCStateAnnouncerLog($"Missing localized name for {nameToken}"); } - catch (KeyNotFoundException) - { - name = "NameNotFound"; - } - //var name = _nameDictionary[nameToken]?.ToString(); return name ?? string.Empty; } + private void WriteNPCStateAnnouncerLog(string message) + { + var e = new LogEvent + { + LogType = LogType.Info, + Tag = "NPC", + Message = message + }; + + Logger.Log(e); + } private string GetStateMessage(NpcStateMessage msg) { From 8fd2ad88e20d7fde250b85775104db1cdadd9793 Mon Sep 17 00:00:00 2001 From: Jeff Maley Date: Thu, 2 Dec 2021 09:52:24 -0800 Subject: [PATCH 349/457] Updated tag for logging method in NPCStateAnnouncer --- .../EventServices/EventProcessors/NpcStateAnnouncer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs index eff5f87dd..c3ad02638 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcStateAnnouncer.cs @@ -64,8 +64,8 @@ private void WriteNPCStateAnnouncerLog(string message) { var e = new LogEvent { - LogType = LogType.Info, - Tag = "NPC", + LogType = LogType.Error, + Tag = "NpcStateAnnouncer", Message = message }; From 4d13f137cf78adb1ffe7b41f3c95a9ea88205f62 Mon Sep 17 00:00:00 2001 From: AqpAnaciy Date: Tue, 29 Nov 2022 13:22:32 +0300 Subject: [PATCH 350/457] New Player Starting NIC/EP - using NIC/EP for new player from server config file, or default value if they are not specified in the file - using this solution for default value: https://stackoverflow.com/questions/29611445/default-value-for-missing-properties-with-json-net --- .../Characters/CharacterCreate.cs | 7 +++++-- src/Perpetuum/GlobalConfiguration.cs | 17 ++++++++++++++++- .../Strategies/TrainingExitStrategy.cs | 11 +++++++---- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs index 508ef53b1..b9ae3cf05 100644 --- a/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs +++ b/src/Perpetuum.RequestHandlers/Characters/CharacterCreate.cs @@ -21,6 +21,7 @@ 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); @@ -31,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; @@ -39,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) @@ -66,7 +69,7 @@ public void HandleRequest(IRequest request) if (account.FirstCharacterDate == null) { account.FirstCharacterDate = DateTime.Now; - _accountManager.AddExtensionPoints(account, 40000); //TODO: starting EP - store in DB + _accountManager.AddExtensionPoints(account, ACCOUNT_START_EP); _accountManager.Repository.Update(account); } diff --git a/src/Perpetuum/GlobalConfiguration.cs b/src/Perpetuum/GlobalConfiguration.cs index 49be1c097..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 @@ -23,5 +26,17 @@ public class GlobalConfiguration 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/Zones/Teleporting/Strategies/TrainingExitStrategy.cs b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs index 48084aac9..23299baa5 100644 --- a/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs +++ b/src/Perpetuum/Zones/Teleporting/Strategies/TrainingExitStrategy.cs @@ -16,8 +16,8 @@ namespace Perpetuum.Zones.Teleporting.Strategies { public class TrainingExitStrategy : ITeleportStrategy { - private const double CHARACTER_START_CREDIT = 500000; //TODO: move to DB - private const double NIC_REWARD_PER_LEVEL = 125000; //TODO: move to DB + 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); @@ -31,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 @@ -202,4 +205,4 @@ private CharacterWizardInfo HardcodedLookUp(TeleportDescription description) } } -} \ No newline at end of file +} From 69bcbf7a0825818ae50e52c81222b78e5eb1597f Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:12:00 +0300 Subject: [PATCH 351/457] fix OpenPerpetuum/PerpetuumServer#460 --- src/Perpetuum/Services/Weather/WeatherInfo.cs | 3 +++ src/Perpetuum/Services/Weather/WeatherService.cs | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Perpetuum/Services/Weather/WeatherInfo.cs b/src/Perpetuum/Services/Weather/WeatherInfo.cs index 859c558cd..a947bb4b1 100644 --- a/src/Perpetuum/Services/Weather/WeatherInfo.cs +++ b/src/Perpetuum/Services/Weather/WeatherInfo.cs @@ -10,6 +10,9 @@ 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; diff --git a/src/Perpetuum/Services/Weather/WeatherService.cs b/src/Perpetuum/Services/Weather/WeatherService.cs index 110d605d4..3c353f543 100644 --- a/src/Perpetuum/Services/Weather/WeatherService.cs +++ b/src/Perpetuum/Services/Weather/WeatherService.cs @@ -6,7 +6,8 @@ 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; @@ -16,9 +17,9 @@ public class WeatherService : Process, IWeatherService 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); } } From 544fdd28bcf8d0b46727aeb07b5e699983c51021 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 23 Aug 2023 19:58:03 +0300 Subject: [PATCH 352/457] Respec Token - Initial commit --- .../PerpetuumBootstrapper.cs | 894 ++++++++++-------- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 3 +- .../DefinitionNames.cs | 1 - .../Extensions/ExtensionResetCharacter.cs | 4 +- .../Perpetuum.RequestHandlers.csproj | 1 + .../SendMessageToCharacterHandler.cs | 24 + .../UseLotteryItem.cs | 214 +++-- src/Perpetuum.ServerService/App.config | 2 +- .../Properties/Settings.Designer.cs | 4 +- .../Properties/Settings.settings | 2 +- src/Perpetuum/Accounting/AccountManager.cs | 322 ++++--- src/Perpetuum/Accounting/IAccountManager.cs | 16 +- src/Perpetuum/Commands.cs | 12 + src/Perpetuum/Containers/Container.Helpers.cs | 7 +- src/Perpetuum/Converters/DelegateConverter.cs | Bin 848 -> 852 bytes src/Perpetuum/ErrorCodes.cs | 4 +- src/Perpetuum/Items/{ => Base}/Item.cs | 156 +-- .../Items/DefaultPropertyModifierReader.cs | 30 + src/Perpetuum/Items/EPBoost.cs | 6 +- .../PropertyModifierCollectionExtensions.cs | 14 + src/Perpetuum/Items/Helpers/ItemHelper.cs | 39 + .../Items/IPropertyModifierCollection.cs | 14 + .../Items/PropertyModifierCollection.cs | 42 + src/Perpetuum/Items/RespecToken.cs | 49 + src/Perpetuum/Perpetuum.csproj | 8 +- .../ProductionEngine/Facilities/Mill.cs | 52 +- .../Facilities/ProductionFacilityEx.cs | 4 +- .../ProductionEngine/Facilities/Prototyper.cs | 38 +- .../Facilities/ResearchLab.cs | 42 +- .../ProductionEngine/PBSFacilities.cs | 36 +- .../ProductionEngine/ProductionInProgress.cs | 228 ++--- .../ProductionEngine/ProductionLine.cs | 277 +++--- .../ProductionEngine/ProductionProcessor.cs | 38 +- src/Perpetuum/Services/TechTree/Points.cs | Bin 1474 -> 1478 bytes .../Services/TechTree/TechTreeNode.cs | Bin 9086 -> 9104 bytes .../TechTree/TechTreePointsHandler.cs | Bin 5518 -> 5528 bytes .../Services/TechTree/TechTreeService.cs | Bin 2842 -> 2844 bytes src/Perpetuum/Threading/LockFree.cs | Bin 1918 -> 1922 bytes .../Units/DockingBases/DockingBaseHelper.cs | 69 +- src/Perpetuum/Wallets/Wallet.cs | Bin 2886 -> 2870 bytes 40 files changed, 1537 insertions(+), 1115 deletions(-) create mode 100644 src/Perpetuum.RequestHandlers/SendMessageToCharacterHandler.cs rename src/Perpetuum/Items/{ => Base}/Item.cs (60%) create mode 100644 src/Perpetuum/Items/DefaultPropertyModifierReader.cs create mode 100644 src/Perpetuum/Items/Extensions/PropertyModifierCollectionExtensions.cs create mode 100644 src/Perpetuum/Items/Helpers/ItemHelper.cs create mode 100644 src/Perpetuum/Items/IPropertyModifierCollection.cs create mode 100644 src/Perpetuum/Items/PropertyModifierCollection.cs create mode 100644 src/Perpetuum/Items/RespecToken.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 97c42a37f..e61a19ee4 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -186,6 +186,7 @@ using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; +using Perpetuum.Items.Helpers; namespace Perpetuum.Bootstrapper { @@ -213,41 +214,46 @@ class TeleportStrategyFactoriesImpl : ITeleportStrategyFactories public class PerpetuumBootstrapper { - private ContainerBuilder _builder; - private IContainer _container; + private ContainerBuilder builder; + private IContainer container; public void Start() { - var s = _container.Resolve(); + var s = container.Resolve(); + s.State = HostState.Starting; } public void Stop() { - var s = _container.Resolve(); + var s = container.Resolve(); + s.State = HostState.Stopping; } public void Stop(TimeSpan delay) { - var m = _container.Resolve(); + var m = container.Resolve(); + m.Shutdown(delay); } public IContainer GetContainer() { - return _container; + return container; } public void WaitForStop() { var are = new AutoResetEvent(false); + var s = container.Resolve(); - var s = _container.Resolve(); s.StateChanged += (sender, state) => { if (state == HostState.Off) + { are.Set(); + } }; are.WaitOne(); @@ -272,94 +278,97 @@ public IEnumerable GetCommands() public void Init(string gameRoot) { - _builder = new ContainerBuilder(); + builder = new ContainerBuilder(); InitContainer(gameRoot); - _container = _builder.Build(); - Logger.Current = _container.Resolve>(); + container = builder.Build(); + Logger.Current = container.Resolve>(); - var config = _container.Resolve(); - _container.Resolve().State = HostState.Init; + var config = container.Resolve(); + container.Resolve().State = HostState.Init; Logger.Info($"Game root: {config.GameRoot}"); Logger.Info($"GC isServerGC: {GCSettings.IsServerGC}"); Logger.Info($"GC Latency mode: {GCSettings.LatencyMode}"); Logger.Info($"Vector is hardware accelerated: {Vector.IsHardwareAccelerated}"); - Db.DbQueryFactory = _container.Resolve>(); + Db.DbQueryFactory = container.Resolve>(); - using (var connection = _container.Resolve()()) { Logger.Info($"Database: {connection.Database}"); } + using (var connection = container.Resolve()()) + { + Logger.Info($"Database: {connection.Database}"); + } - InitGame(_container); + InitGame(container); - EntityDefault.Reader = _container.Resolve(); - Entity.Services = _container.Resolve(); + EntityDefault.Reader = container.Resolve(); + Entity.Services = container.Resolve(); GenxyConverter.RegisterConverter((writer, character) => { GenxyConverter.ConvertInt(writer, character.Id); }); - CorporationData.CorporationManager = _container.Resolve(); + CorporationData.CorporationManager = container.Resolve(); - Character.CharacterFactory = _container.Resolve(); - Character.CharacterCache = _container.Resolve>().Invoke("CharacterCache"); + Character.CharacterFactory = container.Resolve(); + Character.CharacterCache = container.Resolve>().Invoke("CharacterCache"); - MissionHelper.Init(_container.Resolve(), _container.Resolve()); - MissionHelper.MissionProcessor = _container.Resolve(); - MissionHelper.EntityServices = _container.Resolve(); + MissionHelper.Init(container.Resolve(), container.Resolve()); + MissionHelper.MissionProcessor = container.Resolve(); + MissionHelper.EntityServices = container.Resolve(); - Mission.Init(_container.Resolve()); - MissionInProgress.Init(_container.Resolve()); - MissionAgent.Init(_container.Resolve()); - MissionStandingChangeCalculator.Init(_container.Resolve()); - ZoneMissionInProgress.Init(_container.Resolve()); - MissionSpot.Init(_container.Resolve()); - MissionSpot.ZoneManager = _container.Resolve(); - MissionLocation.Init(_container.Resolve()); - MissionLocation.ZoneManager = _container.Resolve(); - MissionTarget.missionDataCache = _container.Resolve(); - MissionTarget.ProductionDataAccess = _container.Resolve(); - MissionTarget.RobotTemplateRelations = _container.Resolve(); - MissionTarget.MissionTargetInProgressFactory = _container.Resolve(); + Mission.Init(container.Resolve()); + MissionInProgress.Init(container.Resolve()); + MissionAgent.Init(container.Resolve()); + MissionStandingChangeCalculator.Init(container.Resolve()); + ZoneMissionInProgress.Init(container.Resolve()); + MissionSpot.Init(container.Resolve()); + MissionSpot.ZoneManager = container.Resolve(); + MissionLocation.Init(container.Resolve()); + MissionLocation.ZoneManager = container.Resolve(); + MissionTarget.missionDataCache = container.Resolve(); + MissionTarget.ProductionDataAccess = container.Resolve(); + MissionTarget.RobotTemplateRelations = container.Resolve(); + MissionTarget.MissionTargetInProgressFactory = container.Resolve(); - MissionTargetRewardCalculator.Init(_container.Resolve()); - MissionTargetSuccessInfoGenerator.Init(_container.Resolve()); - MissionBonus.Init(_container.Resolve()); - ZoneMissionTarget.MissionProcessor = _container.Resolve(); - ZoneMissionTarget.PresenceFactory = _container.Resolve(); + MissionTargetRewardCalculator.Init(container.Resolve()); + MissionTargetSuccessInfoGenerator.Init(container.Resolve()); + MissionBonus.Init(container.Resolve()); + ZoneMissionTarget.MissionProcessor = container.Resolve(); + ZoneMissionTarget.PresenceFactory = container.Resolve(); - MissionResolveTester.Init(_container.Resolve()); - TransportAssignment.EntityServices = _container.Resolve(); - ProductionLine.ProductionLineFactory = _container.Resolve(); - MissionInProgress.MissionInProgressFactory = _container.Resolve(); - MissionInProgress.MissionProcessor = _container.Resolve(); - PriceCalculator.PriceCalculatorFactory = _container.Resolve(); + MissionResolveTester.Init(container.Resolve()); + TransportAssignment.EntityServices = container.Resolve(); + ProductionLine.ProductionLineFactory = container.Resolve(); + MissionInProgress.MissionInProgressFactory = container.Resolve(); + MissionInProgress.MissionProcessor = container.Resolve(); + PriceCalculator.PriceCalculatorFactory = container.Resolve(); - Message.MessageBuilderFactory = _container.Resolve(); + Message.MessageBuilderFactory = container.Resolve(); - PBSHelper.ProductionDataAccess = _container.Resolve(); - PBSHelper.ProductionManager = _container.Resolve(); - PBSHelper.ItemDeployerHelper = _container.Resolve(); + PBSHelper.ProductionDataAccess = container.Resolve(); + PBSHelper.ProductionManager = container.Resolve(); + PBSHelper.ItemDeployerHelper = container.Resolve(); - ProductionComponentCollector.ProductionComponentCollectorFactory = _container.Resolve(); + ProductionComponentCollector.ProductionComponentCollectorFactory = container.Resolve(); - CorporationData.InfoCache = _container.Resolve>().Invoke("CorporationInfoCache"); + CorporationData.InfoCache = container.Resolve>().Invoke("CorporationInfoCache"); - _container.Resolve().StateChanged += (sender, state) => + container.Resolve().StateChanged += (sender, state) => { switch (state) { case HostState.Stopping: { - _container.Resolve().Stop(); + container.Resolve().Stop(); NatDiscoverer.ReleaseAll(); sender.State = HostState.Off; break; } case HostState.Starting: { - _container.Resolve().Start(); + container.Resolve().Start(); sender.State = HostState.Online; break; } @@ -367,28 +376,30 @@ public void Init(string gameRoot) }; DefaultCorporationDataCache.LoadAll(); - _container.Resolve().CacheMissionData(); + container.Resolve().CacheMissionData(); // initialize our markets. // this is dependent on all zones being loaded. - _container.Resolve().Init(); - _container.Resolve().Init(); - - + container.Resolve().Init(); + container.Resolve().Init(); } public bool TryInitUpnp(out bool success) { success = false; - var config = _container.Resolve(); + var config = container.Resolve(); if (!config.EnableUpnp) + { return false; + } try { var discoverer = new NatDiscoverer(); + NatDiscoverer.ReleaseAll(); var natDevice = discoverer.DiscoverDeviceAsync().Result; + if (natDevice == null) { Logger.Error("[UPNP] NAT device not found!"); @@ -406,7 +417,7 @@ void Map(int port) Map(config.ListenerPort); - foreach (var zone in _container.Resolve().Zones) + foreach (var zone in container.Resolve().Zones) { Map(zone.Configuration.ListenerPort); } @@ -427,12 +438,17 @@ 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(); + if (!string.IsNullOrEmpty(globalConfiguration.PersonalConfig)) { - Db.Query().CommandText(globalConfiguration.PersonalConfig).ExecuteNonQuery(); + Db.Query() + .CommandText(globalConfiguration.PersonalConfig) + .ExecuteNonQuery(); Logger.Info("Personal sp executed:" + globalConfiguration.PersonalConfig); } @@ -441,7 +457,7 @@ private static void InitGame(IComponentContext container) private IRegistrationBuilder RegisterAutoActivate(TimeSpan interval) where T : IProcess { - return _builder.RegisterType().SingleInstance().AutoActivate().OnActivated(e => + return builder.RegisterType().SingleInstance().AutoActivate().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(interval)); }); @@ -451,9 +467,7 @@ private void RegisterAutoActivatedTypes() { RegisterAutoActivate(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)); @@ -464,16 +478,20 @@ private void RegisterCommands() { foreach (var 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 => { commandText = commandText.ToUpper(); - return ctx.IsRegisteredWithKey(commandText) ? ctx.ResolveKeyed(commandText) : null; + + return ctx.IsRegisteredWithKey(commandText) + ? ctx.ResolveKeyed(commandText) + : null; }); }); } @@ -498,143 +516,177 @@ private void InitContainer(string gameRoot) RegisterZones(); RegisterPBS(); - _builder.Register>(x => + builder.Register>(x => { return name => new MemoryCache(name); }); - _builder.RegisterType().AsSelf().As(); - _builder.Register(c => - { - var cache = new MemoryCache("CharacterProfiles"); - return new CachedReadOnlyRepository(cache, c.Resolve()); - }).AsSelf().As>().SingleInstance(); + builder.RegisterType().AsSelf().As(); + builder + .Register(c => + { + var cache = new MemoryCache("CharacterProfiles"); - _builder.RegisterType().As(); + return new CachedReadOnlyRepository(cache, c.Resolve()); + }) + .AsSelf() + .As>() + .SingleInstance(); - _builder.RegisterType().As(); - _builder.RegisterType().OnActivated(e => - { - e.Instance.Init(); - }).As().SingleInstance(); + builder.RegisterType().As(); - _builder.RegisterType().As().AutoActivate().OnActivated(e => - { - e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromHours(1))); - }).SingleInstance(); + builder.RegisterType().As(); + builder + .RegisterType() + .OnActivated(e => + { + e.Instance.Init(); + }) + .As() + .SingleInstance(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance().AutoActivate(); + 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().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 => - { - var fileManager = c.Resolve(); - var settingsFile = fileManager.ReadAllText("perpetuum.ini"); - var configuration = JsonConvert.DeserializeObject(settingsFile); - configuration.GameRoot = gameRoot; - return configuration; - }).SingleInstance(); + 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); - _builder.RegisterType().SingleInstance(); + configuration.GameRoot = gameRoot; - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance(); + return configuration; + }) + .SingleInstance(); - _builder.Register(c => - { - var config = c.Resolve(); - return config.Corporation; - }).As(); + builder.RegisterType().SingleInstance(); + + builder.RegisterType(); + builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); + + builder + .Register(c => + { + var config = c.Resolve(); - _builder.RegisterType().As().SingleInstance(); - _builder.Register(c => new ProcessManager(TimeSpan.FromMilliseconds(50))).As().SingleInstance(); + return config.Corporation; + }) + .As(); - _builder.Register(x => + builder.RegisterType().As().SingleInstance(); + builder + .Register(c => new ProcessManager(TimeSpan.FromMilliseconds(50))) + .As() + .SingleInstance(); + + builder.Register(x => { var connectionString = x.Resolve().ConnectionString; + return (() => new SqlConnection(connectionString)); }); - _builder.RegisterType(); + 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(); // this doesn't appear to be something that should be a singleton. + 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 => - { - var ctx = x.Resolve(); - return ((character, type) => + builder.RegisterType().As(); + builder.RegisterType().AsSelf().As(); + builder.RegisterType(); + builder + .Register(x => { - return ctx.Resolve(new TypedParameter(typeof(Character), character), - new TypedParameter(typeof(TransactionType), type)); + var 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 => { if (id == 0) + { return ctx.ResolveNamed("nullcharacter"); + } return ctx.Resolve(TypedParameter.From(id)); }); }); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType().As().SingleInstance(); - - _builder.RegisterType(); - _builder.RegisterType().As(); + builder.RegisterType(); + builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType(); + builder.RegisterType().As(); - _builder.Register(x => + builder.Register(x => { var 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(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); RegisterRequestHandler(Commands.ChannelCreate); RegisterRequestHandler(Commands.ChannelList); @@ -657,99 +709,102 @@ private void RegisterChannelTypes() private void RegisterEffects() { - _builder.RegisterType(); + builder.RegisterType(); - _builder.RegisterType().As(); + builder.RegisterType().As(); - _builder.Register>(x => + builder.Register>(x => { var 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); + 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.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.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 => + builder.Register(x => { var ctx = x.Resolve(); return effectType => { if (!ctx.IsRegisteredWithKey(effectType)) + { return new Effect(); + } return ctx.ResolveKeyed(effectType); }; @@ -758,13 +813,17 @@ private void RegisterEffects() 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 { - return _builder.RegisterType().OnActivated(e => + return builder.RegisterType().OnActivated(e => { e.Instance.EntityServices = e.Context.Resolve(); }); @@ -795,7 +854,7 @@ private IRegistrationBuilder() where T : Corporation { - _builder.RegisterType(); + builder.RegisterType(); RegisterEntity().PropertiesAutowired(); } @@ -826,20 +885,24 @@ public IRegistrationBuilder(); - _builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); - _builder.RegisterType().As().SingleInstance().OnActivated(e => e.Instance.Init()); - _builder.RegisterType().As(); + builder + .RegisterType() + .As() + .SingleInstance() + .OnActivated(e => e.Instance.Init()); + builder.RegisterType().As(); - _builder.RegisterType().OnActivated(e => e.Instance.Init()).SingleInstance(); + builder.RegisterType().OnActivated(e => e.Instance.Init()).SingleInstance(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType(); + builder.RegisterType(); - _builder.RegisterType().AsSelf().As(); + builder.RegisterType().AsSelf().As(); InitItems(); @@ -848,7 +911,7 @@ private void RegisterEntities() RegisterRobot(); RegisterRobot(); - _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); + builder.RegisterType().As().PropertiesAutowired().SingleInstance(); RegisterEntity(); @@ -1041,9 +1104,10 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity(); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); @@ -1254,6 +1318,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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 // OPP new Blinder module ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); @@ -1347,7 +1412,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] private void RegisterLoggers() { - _builder.Register(x => + builder.Register(x => { return new LoggerCache(new MemoryCache("LoggerCache")) { @@ -1355,7 +1420,7 @@ private void RegisterLoggers() }; }).As().SingleInstance(); - _builder.Register(x => + builder.Register(x => { var ctx = x.Resolve(); return name => @@ -1365,9 +1430,9 @@ private void RegisterLoggers() }; }); - _builder.RegisterGeneric(typeof(FileLogger<>)); + builder.RegisterGeneric(typeof(FileLogger<>)); - _builder.Register>>(x => + builder.Register>>(x => { var ctx = x.Resolve(); return ((directory, filename) => @@ -1377,7 +1442,7 @@ private void RegisterLoggers() }); }); - _builder.Register(x => + builder.Register(x => { var ctx = x.Resolve(); return (directory, filename) => @@ -1388,7 +1453,7 @@ private void RegisterLoggers() }); - _builder.Register(c => + builder.Register(c => { var defaultFormater = new DefaultLogEventFormatter(); @@ -1419,24 +1484,24 @@ private void RegisterLoggers() 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 { - return _builder.RegisterType().Keyed(presenceType).PropertiesAutowired(); + 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(); @@ -1445,39 +1510,39 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { - _builder.RegisterType().As(); - _builder.RegisterType().SingleInstance(); - _builder.RegisterType().SingleInstance().As(); + 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().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().SingleInstance(); + builder.RegisterType().As().SingleInstance(); - _builder.RegisterType() + 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 => + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().OnActivated(e => { var pm = e.Context.Resolve(); pm.AddProcess(e.Instance.AsTimed(TimeSpan.FromSeconds(2)).ToAsync()); @@ -1486,7 +1551,7 @@ public void RegisterNpcs() }).As(); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -1497,7 +1562,7 @@ public void RegisterNpcs() }; }); - _builder.Register(x => + builder.Register(x => { var ctx = x.Resolve(); @@ -1538,7 +1603,7 @@ public void RegisterNpcs() RegisterPresence(PresenceType.EscalatingRandomPresence); RegisterPresence(PresenceType.GrowingNPCBasePresence); - _builder.Register(x => + builder.Register(x => { var ctx = x.Resolve(); return ((zone, configuration) => @@ -1570,19 +1635,19 @@ public void RegisterNpcs() private void RegisterMtProducts() { - _builder.RegisterType().As(); - _builder.RegisterType(); + 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 => + builder.RegisterType(); + builder.RegisterType().SingleInstance(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType().OnActivated(e => { var pm = e.Context.Resolve(); pm.AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(1))); @@ -1603,118 +1668,118 @@ private void RegisterMissions() RegisterRequestHandler(Commands.MissionReset); RegisterRequestHandler(Commands.MissionListAgents); - _builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); } private void InitRelayManager() { - _builder.RegisterType().SingleInstance(); - _builder.RegisterType().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().As(); - _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().SingleInstance().As(); + builder.RegisterType().SingleInstance().As(); - _builder.RegisterType().SingleInstance(); + 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(); 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(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType(); // OPP: EPBonusEventService singleton - _builder.RegisterType().SingleInstance().OnActivated(e => + 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 => + 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()); @@ -1726,7 +1791,7 @@ private void InitRelayManager() obs.Subscribe(e.Context.Resolve()); }); - _builder.RegisterType().As().SingleInstance().OnActivated(e => + builder.RegisterType().As().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1.5))); }); @@ -1734,11 +1799,11 @@ private void InitRelayManager() // 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) => @@ -1747,16 +1812,16 @@ private void InitRelayManager() new TypedParameter(typeof(AccountTransactionType), type)); }); }); - _builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); } private IRegistrationBuilder RegisterRequestHandler(Command command) where TRequestHandler:IRequestHandler where TRequest : IRequest { - var res = _builder.RegisterType(); + var res = builder.RegisterType(); - _builder.Register(c => + builder.Register(c => { return c.Resolve>(new TypedParameter(typeof(IRequestHandler), c.Resolve())); }).Keyed>(command); @@ -1771,7 +1836,7 @@ private IRegistrationBuilder() where T : IRequest { - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return (command => @@ -1783,7 +1848,7 @@ private void RegisterRequestHandlerFactory() where T : IRequest private void RegisterRequestHandlers() { - _builder.RegisterGeneric(typeof(RequestHandlerProfiler<>)); + builder.RegisterGeneric(typeof(RequestHandlerProfiler<>)); RegisterRequestHandlerFactory(); RegisterRequestHandlerFactory(); @@ -2092,6 +2157,7 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.SetItemName); RegisterRequestHandler(Commands.StackTo); RegisterRequestHandler(Commands.ServerMessage); + //RegisterRequestHandler(Commands.SendMessageToCharacter); RegisterRequestHandler(Commands.RequestInfiniteBox); RegisterRequestHandler(Commands.DecorCategoryList); RegisterRequestHandler(Commands.PollGet); @@ -2246,7 +2312,7 @@ private void RegisterRequestHandlers() private void RegisterRobotTemplates() { - _builder.Register(x => + builder.Register(x => { var relations = x.Resolve(); return (definition => @@ -2255,28 +2321,28 @@ private void RegisterRobotTemplates() }); }); - _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(); return zone => @@ -2326,15 +2392,15 @@ 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(); return zone => @@ -2417,7 +2483,7 @@ private void RegisterIntrusions() private void RegisterRifts() { - _builder.Register>(x => + builder.Register>(x => { return zone => { @@ -2430,10 +2496,10 @@ private void RegisterRifts() }; }); - _builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2481,9 +2547,9 @@ private void RegisterRifts() private void RegisterRelics() { - _builder.RegisterType().As(); + builder.RegisterType().As(); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2517,14 +2583,14 @@ private void RegisterRelics() 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 => @@ -2532,9 +2598,9 @@ private void RegisterZones() e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5))); }).As(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.Register>(x => + builder.RegisterType(); + builder.RegisterType(); + builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2543,7 +2609,7 @@ private void RegisterZones() }; }); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2554,10 +2620,10 @@ private void RegisterZones() }; }); - _builder.RegisterType().AsSelf().As(); - _builder.RegisterType().AsSelf().As(); + builder.RegisterType().AsSelf().As(); + builder.RegisterType().AsSelf().As(); - _builder.Register(x => + builder.Register(x => { var ctx = x.Resolve(); return zone => @@ -2570,14 +2636,14 @@ private void RegisterZones() }; }); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType().As().SingleInstance(); 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()); }); @@ -2588,13 +2654,13 @@ void RegisterZone(ZoneType type) where T:Zone RegisterZone(ZoneType.Training); RegisterZone(ZoneType.Stronghold); - _builder.RegisterType(); - _builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); - _builder.RegisterType().As(); + builder.RegisterType().As(); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return zone => @@ -2603,7 +2669,7 @@ void RegisterZone(ZoneType type) where T:Zone }; }); - _builder.Register>(x => + builder.Register>(x => { var ctx = x.Resolve(); return configuration => @@ -2651,8 +2717,8 @@ void RegisterZone(ZoneType type) where T: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()) { @@ -2671,46 +2737,46 @@ void RegisterZone(ZoneType type) where T:Zone }; }).SingleInstance(); - _builder.RegisterType(); + builder.RegisterType(); - _builder.RegisterType().OnActivated(e => + builder.RegisterType().OnActivated(e => { var 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(); pm.AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromSeconds(5))); }).As().InstancePerDependency(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance(); - _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(); 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 @@ -2859,9 +2925,9 @@ private void RegisterZoneRequestHandlers() 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/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index cb9b634b7..bb9aff1c7 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -664,6 +664,7 @@ public enum CategoryFlags : long cf_world_mobile_teleport_capsule = 0x0000000000020198, cf_zone_item_shop = 0x0000000000000796, cf_zone_objects = 0x0000000000000092, - def_pbs_dogtags = 0x0000000000000691 + def_pbs_dogtags = 0x0000000000000691, + cf_respec_tokens = 0x000000000004049B, } } diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index e0a1ad26e..65066a740 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -4992,6 +4992,5 @@ public static class DefinitionNames public const string ZMALL_RAILG00N = "def_zmall_railg00n"; public const string ZONE_STORAGE = "def_zone_storage"; public const string ZUPALIGHT = "def_zupalight"; - } } diff --git a/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs b/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs index a1e6edc7e..d726fb5bb 100644 --- a/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs +++ b/src/Perpetuum.RequestHandlers/Extensions/ExtensionResetCharacter.cs @@ -43,6 +43,7 @@ public void HandleRequest(IRequest request) (character.IsDocked).ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); + /* var product = _mtProductHelper.GetByAccountTransactionType(AccountTransactionType.ExtensionReset); var wallet = _accountManager.GetWallet(account,AccountTransactionType.ExtensionReset); @@ -53,10 +54,11 @@ public void HandleRequest(IRequest request) Credit = wallet.Balance, CreditChange = -product.price }; - + _accountManager.LogTransaction(e); _accountRepository.Update(account); + */ //current extensions var extensionCollection = character.GetExtensions(); diff --git a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj index a0a729793..78dc42295 100644 --- a/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj +++ b/src/Perpetuum.RequestHandlers/Perpetuum.RequestHandlers.csproj @@ -212,6 +212,7 @@ + diff --git a/src/Perpetuum.RequestHandlers/SendMessageToCharacterHandler.cs b/src/Perpetuum.RequestHandlers/SendMessageToCharacterHandler.cs new file mode 100644 index 000000000..0957dc589 --- /dev/null +++ b/src/Perpetuum.RequestHandlers/SendMessageToCharacterHandler.cs @@ -0,0 +1,24 @@ +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 SendMessageToCharacterHandler : IRequestHandler + { + public void HandleRequest(IZoneRequest request) + { + var messageBuilder = Message.Builder.SetCommand(Commands.ServerMessage).WithData(new Dictionary(request.Data)); + Message.Builder.FromRequest(request).WithOk().Send(); + + Message.Builder + .SetCommand(Commands.ServerMessage) + .WithData(new Dictionary(request.Data)) + .ToCharacter(request.Session.Character) + .Send(); + } + } +} diff --git a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index e0ec9e479..7fb990f8f 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using System.Transactions; using Perpetuum.Accounting; @@ -6,6 +7,7 @@ using Perpetuum.Containers; using Perpetuum.Data; using Perpetuum.EntityFramework; +using Perpetuum.GenXY; using Perpetuum.Host.Requests; using Perpetuum.Items; using Perpetuum.Robots; @@ -14,37 +16,93 @@ namespace Perpetuum.RequestHandlers { public class UseLotteryItem : IRequestHandler { - private readonly IEntityServices _entityServices; + private readonly IEntityServices entityServices; private readonly IAccountManager accountManager; + private readonly IAccountRepository accountRepository; - public UseLotteryItem(IEntityServices entityServices, IAccountManager accountManager) + public UseLotteryItem( + IEntityServices entityServices, + IAccountManager accountManager, + IAccountRepository accountRepository) { - _entityServices = entityServices; - this.accountManager = accountManager; + this.entityServices = entityServices; + this.accountManager = accountManager; + this.accountRepository = accountRepository; } - public void HandleRequest(IRequest request) { var itemEid = request.Data.GetOrDefault(k.itemEID); - var item = _entityServices.Repository.Load(itemEid); + var item = entityServices.Repository.Load(itemEid); + if (item is LotteryItem) + { HandleLottery(request); + } else if (item is EPBoost) - HandleEPBoost(request, itemEid); + { + 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); + } + } + + private void HandleLottery(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var itemEid = request.Data.GetOrDefault(k.itemEID); + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; + + var container = Container.GetWithItems(containerEid, character); + var lotteryItem = (LotteryItem)container.GetItemOrThrow(itemEid, true).Unstack(1); + + var randomEd = lotteryItem.PickRandomItem(); + var randomItem = (Item)entityServices.Factory.CreateWithRandomEID(randomEd); + randomItem.Owner = character.Eid; + + container.AddItem(randomItem, true); + entityServices.Repository.Delete(lotteryItem); + container.Save(); + + LogOpen(character, container, lotteryItem); + LogRandomItemCreated(character, container, randomItem); + + Transaction.Current.OnCommited(() => + { + var result = new Dictionary + { + {k.container,container.ToDictionary()}, + {k.item,randomItem.ToDictionary()} + }; + + Message.Builder.FromRequest(request).WithData(result).Send(); + }); + + scope.Complete(); + } } - private void HandleEPBoost(IRequest request, long itemEid) + 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 account = accountManager.Repository + .Get(request.Session.AccountId) + .ThrowIfNull(ErrorCodes.AccountNotFound); var container = Container.GetWithItems(containerEid, character); @@ -52,10 +110,9 @@ private void HandleEPBoost(IRequest request, long itemEid) containerItem.Activate(accountManager, account); - _entityServices.Repository.Delete(containerItem); + entityServices.Repository.Delete(containerItem); container.Save(); LogActivation(character, container, containerItem); - Transaction.Current.OnCommited(() => { @@ -88,11 +145,11 @@ private void HandleCalibrationTemplateItem(IRequest request, long itemEid) var ctCapsule = (CalibrationProgramCapsule)container.GetItemOrThrow(itemEid, true).Unstack(1); var ctDef = ctCapsule.Activate(); - var ctItem = (Item)_entityServices.Factory.CreateWithRandomEID(ctDef); + var ctItem = (Item)entityServices.Factory.CreateWithRandomEID(ctDef); ctItem.Owner = character.Eid; container.AddItem(ctItem, false); // CTs dont stack - _entityServices.Repository.Delete(ctCapsule); + entityServices.Repository.Delete(ctCapsule); container.Save(); Transaction.Current.OnCommited(() => @@ -109,72 +166,39 @@ private void HandleCalibrationTemplateItem(IRequest request, long itemEid) } } - public void HandleLottery(IRequest request) - { - using (var scope = Db.CreateTransaction()) - { - var itemEid = request.Data.GetOrDefault(k.itemEID); - var containerEid = request.Data.GetOrDefault(k.containerEID); - var character = request.Session.Character; - - var container = Container.GetWithItems(containerEid, character); - var lotteryItem = (LotteryItem)container.GetItemOrThrow(itemEid, true).Unstack(1); - - var randomEd = lotteryItem.PickRandomItem(); - var randomItem = (Item)_entityServices.Factory.CreateWithRandomEID(randomEd); - randomItem.Owner = character.Eid; - - container.AddItem(randomItem, true); - _entityServices.Repository.Delete(lotteryItem); - container.Save(); - - LogOpen(character, container, lotteryItem); - LogRandomItemCreated(character, container, randomItem); - - Transaction.Current.OnCommited(() => - { - var result = new Dictionary - { - {k.container,container.ToDictionary()}, - {k.item,randomItem.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); + 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()) @@ -188,7 +212,7 @@ private void HandlePaint(IRequest request, long paintEid) var paintItem = (Paint)container.GetItemOrThrow(paintEid, true).Unstack(1); paintItem.Activate(container as RobotInventory, character); - _entityServices.Repository.Delete(paintItem); + entityServices.Repository.Delete(paintItem); container.Save(); Transaction.Current.OnCommited(() => @@ -207,5 +231,65 @@ private void HandlePaint(IRequest request, long paintEid) 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); + + //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(); + } + + var relogMessage = new Dictionary + { + { k.message, "Relog required for changes to take effect" }, + { k.translate, "relog_required_message" }, + }; + + Message.Builder + .SetCommand(Commands.ServerMessage) + .WithData(relogMessage) + .ToCharacter(request.Session.Character) + .Send(); + } } } \ No newline at end of file diff --git a/src/Perpetuum.ServerService/App.config b/src/Perpetuum.ServerService/App.config index 56c89d4da..5670c46cb 100644 --- a/src/Perpetuum.ServerService/App.config +++ b/src/Perpetuum.ServerService/App.config @@ -11,7 +11,7 @@ - C:\PerpetuumServer\data\ + F:\PerpetuumServer\data\ diff --git a/src/Perpetuum.ServerService/Properties/Settings.Designer.cs b/src/Perpetuum.ServerService/Properties/Settings.Designer.cs index 43ce4cc26..9da1d2458 100644 --- a/src/Perpetuum.ServerService/Properties/Settings.Designer.cs +++ b/src/Perpetuum.ServerService/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Perpetuum.ServerService.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -25,7 +25,7 @@ public static Settings Default { [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("C:\\PerpetuumServer\\data\\")] + [global::System.Configuration.DefaultSettingValueAttribute("F:\\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 index ae61c7ea2..b13297262 100644 --- a/src/Perpetuum.ServerService/Properties/Settings.settings +++ b/src/Perpetuum.ServerService/Properties/Settings.settings @@ -3,7 +3,7 @@ - C:\PerpetuumServer\data\ + F:\PerpetuumServer\data\ \ No newline at end of file diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 4ca8ef94a..c0fc6ceb4 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -13,10 +13,13 @@ namespace Perpetuum.Accounting { public class AccountManager : IAccountManager { - private readonly AccountTransactionLogger _transactionLogger; - private readonly AccountWalletFactory _walletFactory; - private readonly EpForActivityLogger _epForActivityLogger; - private readonly EPBonusEventService _epBonusEventService; + 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, @@ -25,22 +28,22 @@ public AccountManager(IAccountRepository accountRepository, EPBonusEventService epBonusEventService) { Repository = accountRepository; - _transactionLogger = transactionLogger; - _walletFactory = walletFactory; - _epForActivityLogger = epForActivityLogger; - _epBonusEventService = epBonusEventService; + this.transactionLogger = transactionLogger; + this.walletFactory = walletFactory; + this.epForActivityLogger = epForActivityLogger; + this.epBonusEventService = epBonusEventService; } public IAccountRepository Repository { get; } 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) @@ -48,13 +51,15 @@ public IEnumerable GetTransactionHistory(Account acc 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") + 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) { Definition = r.GetValue("definition"), @@ -77,7 +82,8 @@ public int GetActiveCharactersCount(Account account) public Character[] GetDeletedCharacters(Account account) { - return Db.Query().CommandText("select characterid from characters where accountid=@accountID and deletedat is not null and active=0") + 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(); @@ -86,9 +92,12 @@ public Character[] GetDeletedCharacters(Account account) 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") + 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(); @@ -98,17 +107,17 @@ 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); + return lockedEp; } - private const double BOOSTMULTIPLIERMAX = 25; - private const double SERVER_DESIRED_EP_LEVEL = 1000000; - private const double GAURANTEED_BOOST_MAX_THRESH = 45000; - public IDictionary GetEPData(Account account,Character character) { var availablePoints = CalculateCurrentEp(account); @@ -117,7 +126,8 @@ public IDictionary GetEPData(Account account,Character character) var lockedEPPerAccount = GetLockedEpByAccount(account); var epCollected = GetExtensionPointsCollected(account); var boostFactor = GetExperienceBoostingFactor(epCollected,SERVER_DESIRED_EP_LEVEL); - var onePointRate = CalculateBoostedExtensionPoint(1,GetBoostMultiplier(boostFactor, GetEpBonusFromEvent(), GetEpBonusFromSubscription(account))); + var onePointRate = + CalculateBoostedExtensionPoint(1, GetBoostMultiplier(boostFactor, GetEpBonusFromEvent(), GetEpBonusFromSubscription(account))); onePointRate = account.IsDailyEpBoosted ? onePointRate * 2 : onePointRate; var result = new Dictionary @@ -135,47 +145,13 @@ public IDictionary GetEPData(Account account,Character character) return result; } - /// - /// 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; - } - public int CalculateCurrentEp(Account account) { var dailySum = GetDailyPointsSum(account); var penaltySum = GetPenaltyPointsSum(account); var ingameSpent = GetSumExtensionPointsSpent(account); var resultEp = dailySum - penaltySum - ingameSpent; + return resultEp; } @@ -185,7 +161,8 @@ public int CalculateCurrentEp(Account account) /// public int GetDailyPointsSum(Account account) { - var result = Db.Query().CommandText("SELECT SUM(points) FROM extensionpoints WHERE accountid=@accountID") + var result = Db.Query() + .CommandText("SELECT SUM(points) FROM extensionpoints WHERE accountid=@accountID") .SetParameter("@accountID",account.Id) .ExecuteScalar(); @@ -198,7 +175,8 @@ public int GetDailyPointsSum(Account account) /// public int GetPenaltyPointsSum(Account account) { - var result = Db.Query().CommandText("select sum(points) from extensionpointpenalty where accountid=@accountID") + var result = Db.Query() + .CommandText("select sum(points) from extensionpointpenalty where accountid=@accountID") .SetParameter("@accountID",account.Id) .ExecuteScalar(); @@ -210,7 +188,8 @@ public int GetPenaltyPointsSum(Account account) /// public int GetSumExtensionPointsSpentByCharacter(Account account,Character character) { - return Db.Query().CommandText("select sum(points) from accountextensionspent WHERE accountID=@accountID and characterid=@characterID") + return Db.Query() + .CommandText("select sum(points) from accountextensionspent WHERE accountID=@accountID and characterid=@characterID") .SetParameter("@accountID",account.Id) .SetParameter("@characterID",character.Id) .ExecuteScalar(); @@ -222,9 +201,11 @@ 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") + var spentSum = Db.Query() + .CommandText("SELECT SUM(points) FROM accountextensionspent WHERE accountID=@accountID") .SetParameter("@accountID",account.Id) .ExecuteScalar(); + return spentSum; } @@ -233,6 +214,7 @@ public int GetExtensionPointsCollected(Account account) var collectedEp = Db.Query().CommandText("SELECT dbo.extensionPointsCollected(@accountID)") .SetParameter("@accountID",account.Id) .ExecuteScalar(); + return collectedEp; } @@ -244,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()})") + 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; @@ -289,21 +276,24 @@ 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) { - 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) + Db.Query() + .CommandText(sqlInsertCommand) .SetParameter("@accountID",account.Id) .SetParameter("@characterID",character.Id) .SetParameter("@extensionID",extensionId) @@ -317,7 +307,8 @@ public void InsertExtensionRemoveLog(Account account,Character character,int ext /// 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)") + 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) @@ -333,7 +324,8 @@ public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTi { try { - Db.Query().CommandText("opp.extensionSubscriptionStart") + Db.Query() + .CommandText("opp.extensionSubscriptionStart") .SetParameter("@accountID", account.Id) .SetParameter("@startTime", startTime) .SetParameter("@endTime", endTime) @@ -346,22 +338,11 @@ public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTi } } - //public void ExtensionSubscriptionExtend(Account account,DateTime extendedValidUntil) - //{ - // Db.Query().CommandText("extensionSubscriptionExtend") - // .SetParameter("@accountID",account.Id) - // .SetParameter("@endTime",extendedValidUntil) - // .ExecuteNonQuery(); - //} - - /// - /// penalty IS ++++ positive - /// injection IS ---- negative - /// 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 )") + 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) @@ -372,30 +353,12 @@ public void InsertPenaltyPoint(Account account,AccountExtensionPenaltyType penal (affected == 1).ThrowIfFalse(ErrorCodes.SQLInsertError); } - 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); - } - public int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType, int points) { if (points <= 0) + { return 0; + } var bonusIncrease = GetEpBonusFromEvent(); var itemIncrease = GetEpBonusFromSubscription(account); @@ -405,7 +368,15 @@ public int AddExtensionPointsBoostAndLog(Account account, Character character, E Transaction.Current.OnCommited(() => { - LogEpForActivity(account, character, activityType, rawPoints, boostedPoints, boostFactor, (int)BOOSTMULTIPLIERMAX, (int)bonusIncrease); + LogEpForActivity( + account, + character, + activityType, + rawPoints, + boostedPoints, + boostFactor, + (int)BOOSTMULTIPLIERMAX, + (int)bonusIncrease); }); AddExtensionPoints(account, boostedPoints); @@ -425,11 +396,104 @@ public void AddExtensionPoints(Account account,int pointsToInject) }); } - private void InjectExtensionPoints(Account account,int points) + public IEnumerable GetEpForActivityHistory(Account account,DateTime earlier,DateTime later) { - Db.Query().CommandText("extensionPointsInject") - .SetParameter("@accountID",account.Id) - .SetParameter("@points",points) + 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) .ExecuteNonQuery(); } @@ -444,7 +508,10 @@ private int GetBoostedExtensionPoints(Account account, int points, double bonusI var dailyPointsSum = GetExtensionPointsCollected(account); var realEpGain = AddExtensionPointWithBoosting(points, dailyPointsSum, SERVER_DESIRED_EP_LEVEL, bonusIncrease, itemIncrease); if (account.IsDailyEpBoosted) + { realEpGain *= 2; + } + return realEpGain; } @@ -456,7 +523,12 @@ private int GetBoostedExtensionPoints(Account account, int points, double bonusI /// /// Any multiplier increase over the BOOSTMULTIPLIERMAX /// - private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusIncrease, int itemIncrease) + private static int AddExtensionPointWithBoosting( + int extensionPointsToAdd, + int accoutEpSum, + double epLevelThreshold, + double bonusIncrease, + int itemIncrease) { var result = 0; var currentEp = accoutEpSum; @@ -473,7 +545,15 @@ private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int a return Math.Max(result, extensionPointsToAdd); } - private void LogEpForActivity(Account account, Character character, EpForActivityType activityType, int rawPoints, int points, double boostFactor, int bonusMultiplier, int additionalMultiplier) + private void LogEpForActivity( + Account account, + Character character, + EpForActivityType activityType, + int rawPoints, + int points, + double boostFactor, + int bonusMultiplier, + int additionalMultiplier) { var epForActivityLogEvent = new EpForActivityLogEvent(activityType) { @@ -486,36 +566,14 @@ private void LogEpForActivity(Account account, Character character, EpForActivit EventMultiplier = additionalMultiplier, }; - _epForActivityLogger.Log(epForActivityLogEvent); + 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) - { - 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) + /// + public void UnlockEpAndReset(Account account, Character character) { - // 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/IAccountManager.cs b/src/Perpetuum/Accounting/IAccountManager.cs index 95b678f40..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, int multiplierBonus); - //void ExtensionSubscriptionExtend(Account account, DateTime extendedValidUntil); 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/Commands.cs b/src/Perpetuum/Commands.cs index c2cdf63c0..a2773eb9b 100644 --- a/src/Perpetuum/Commands.cs +++ b/src/Perpetuum/Commands.cs @@ -908,6 +908,18 @@ public static Command GetCommandByText(string commandText) } }; + public static readonly Command SendMessageToCharacter = new Command + { + Text = "sendMessageToCharacter", + Arguments = + { + new Argument(k.message), + new Argument(k.type), + new Argument(k.recipients), + new Argument(k.translate) + } + }; + public static readonly Command UpdateMoodMessage = new Command { Text = "update_moodMessage", diff --git a/src/Perpetuum/Containers/Container.Helpers.cs b/src/Perpetuum/Containers/Container.Helpers.cs index 5072352bc..a0e84f37e 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; @@ -88,16 +89,16 @@ public static PublicContainer GetFromStructure(long strucureEid) public class ContainerHelper { - private readonly ItemHelper _itemHelper; + private readonly ItemHelper itemHelper; public ContainerHelper(ItemHelper itemHelper) { - _itemHelper = itemHelper; + this.itemHelper = itemHelper; } public PublicContainer GetFromStructure(long strucureEid) { - var entity = _itemHelper.LoadItem(strucureEid); + var entity = itemHelper.LoadItem(strucureEid); if (entity is DockingBase dockingBase) { diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs index 131164f216c72edccb5c345143022be8e314d734..227d04a85db80b19fc12c54e25df396d2eb1f37a 100644 GIT binary patch delta 20 ccmcb>c7<)ii-|jUCVqM~aR=Y#7mOK<0CGYKM*si- delta 14 Wcmcb@c7biei;1tkY<|F4!UzC7PY0m@ diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index f28d8eb1d..63e9efa89 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -709,6 +709,8 @@ public enum ErrorCodes OnlyBuildableOnTerraformProtected, TechLevelTooLow, MaxExpiringBasePerZoneReached, - MaxExpiringBasePerZonePerCorpReached + MaxExpiringBasePerZonePerCorpReached, + TooMuchEp, + RelogRequired, } } 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/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 index d95dec13a..91a5efffe 100644 --- a/src/Perpetuum/Items/EPBoost.cs +++ b/src/Perpetuum/Items/EPBoost.cs @@ -1,9 +1,5 @@ using Perpetuum.Accounting; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Perpetuum.Items { @@ -35,7 +31,7 @@ public string Tier public void Activate(IAccountManager accountManager, Account account) { - accountManager.ExtensionSubscriptionStart(account, DateTime.Now, DateTime.Now.AddHours(TimePeriodHours), Boost); + 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/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..4717fbdd0 --- /dev/null +++ b/src/Perpetuum/Items/RespecToken.cs @@ -0,0 +1,49 @@ +using Perpetuum.Accounting; +using Perpetuum.Accounting.Characters; + +namespace Perpetuum.Items +{ + /// + /// Represents respec token item + /// + public class RespecToken : Item + { + public int EpLimit + { + get + { + return ED.Options.GetOption("epLimit"); + } + } + + public string Tier + { + get + { + return ED.Options.GetOption("tier"); + } + } + + public void Activate( + IAccountManager accountManager, + Account account, + Character character) + { + var epData = accountManager.GetEPData(account, character); + var avaliableEp = (int)epData[k.points]; + var spentEpPerCharacter = (int)epData["spentEpPerCharacter"]; + + if (this.EpLimit >= avaliableEp + spentEpPerCharacter) + { + accountManager.FreeLockedEp(account, (int)epData[k.lockedEp]); + character.ResetAllExtensions(); + var defaultExtensions = character.GetDefaultExtensions(); + character.SetExtensions(defaultExtensions); + } + else + { + throw PerpetuumException.Create(ErrorCodes.TooMuchEp); + } + } + } +} diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 38a9c86aa..0ef46e56a 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -218,8 +218,14 @@ + + + + + + @@ -379,7 +385,7 @@ - + diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs index be60dc61f..3ad46ab23 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs @@ -265,18 +265,18 @@ public ProductionInProgress LineStart(Character character, ProductionLine produc } var newProduction = ProductionInProgressFactory(); - newProduction.startTime = DateTime.Now; - newProduction.finishTime = DateTime.Now.AddSeconds(productionTimeSeconds); - newProduction.type = ProductionInProgressType.massProduction; - newProduction.character = character; - newProduction.facilityEID = Eid; - newProduction.resultDefinition = productionLine.TargetDefinition; - newProduction.totalProductionTimeSeconds = productionTimeSeconds; - newProduction.baseEID = Parent; - newProduction.pricePerSecond = cprg.IsMissionRelated ? 0.0 : GetPricePerSecond(productionLine.TargetDefinition); + newProduction.StartTime = DateTime.Now; + newProduction.FinishTime = DateTime.Now.AddSeconds(productionTimeSeconds); + newProduction.Type = ProductionInProgressType.massProduction; + newProduction.Character = character; + newProduction.FacilityEID = Eid; + newProduction.ResultDefinition = productionLine.TargetDefinition; + newProduction.TotalProductionTimeSeconds = productionTimeSeconds; + newProduction.BaseEID = Parent; + newProduction.PricePerSecond = cprg.IsMissionRelated ? 0.0 : GetPricePerSecond(productionLine.TargetDefinition); newProduction.ReservedEids = reservedEids; - newProduction.amountOfCycles = cycles; - newProduction.useCorporationWallet = useCorporationWallet; + newProduction.AmountOfCycles = cycles; + newProduction.UseCorporationWallet = useCorporationWallet; if (!newProduction.TryWithdrawCredit()) { @@ -394,32 +394,32 @@ private IDictionary EndMassProduction(ProductionInProgress produ //delete the used items foreach (var item in productionInProgress.GetReservedItems()) { - productionInProgress.character.LogTransaction(TransactionLogEvent.Builder() + productionInProgress.Character.LogTransaction(TransactionLogEvent.Builder() .SetTransactionType(TransactionType.MassProductionDeleted) - .SetCharacter(productionInProgress.character) + .SetCharacter(productionInProgress.Character) .SetItem(item)); Repository.Delete(item); } //pick the output defintion--------------------------------------------------- - var outputDefinition = productionInProgress.resultDefinition; + var outputDefinition = productionInProgress.ResultDefinition; //load container var container = (PublicContainer)Container.GetOrThrow(PublicContainerEid); - container.ReloadItems(productionInProgress.character); + container.ReloadItems(productionInProgress.Character); var outputDefault = EntityDefault.Get(outputDefinition).ThrowIfEqual(EntityDefault.None, ErrorCodes.DefinitionNotSupported); //create item var resultItem = container.CreateAndAddItem(outputDefinition, false, item => { - item.Owner = productionInProgress.character.Eid; - item.Quantity = outputDefault.Quantity * productionInProgress.amountOfCycles; + item.Owner = productionInProgress.Character.Eid; + item.Quantity = outputDefault.Quantity * productionInProgress.AmountOfCycles; }); - productionInProgress.character.WriteItemTransactionLog(TransactionType.MassProductionCreated, resultItem); + productionInProgress.Character.WriteItemTransactionLog(TransactionType.MassProductionCreated, resultItem); CalibrationProgram calibrationProgram; var wasLineDead = false; @@ -438,7 +438,7 @@ private IDictionary EndMassProduction(ProductionInProgress produ //do production rounds //ThreadPoolHelper.ScheduledTask(3000, () => TryNextRound(productionInProgress.character, affectedProductionLine.ID, productionInProgress.amountOfCycles, productionInProgress.useCorporationWallet)); - var nrp = new NextRoundProduction(ProductionProcessor, productionInProgress.character, affectedProductionLine.Id, productionInProgress.amountOfCycles, productionInProgress.useCorporationWallet, Eid); + var nrp = new NextRoundProduction(ProductionProcessor, productionInProgress.Character, affectedProductionLine.Id, productionInProgress.AmountOfCycles, productionInProgress.UseCorporationWallet, Eid); ProductionProcessor.EnqueueNextRoundProduction(nrp); } @@ -462,7 +462,7 @@ private IDictionary EndMassProduction(ProductionInProgress produ container.Save(); //get list in order to return - var linesList = GetLinesList(productionInProgress.character); + var linesList = GetLinesList(productionInProgress.Character); Logger.Info("Mass Production created an item: " + resultItem + " production:" + productionInProgress); @@ -473,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; } @@ -570,7 +570,7 @@ public override IDictionary CancelProduction(ProductionInProgres var replyDict = ReturnReservedItems(productionInProgress); //return list - var linesList = GetLinesList(productionInProgress.character); + var linesList = GetLinesList(productionInProgress.Character); replyDict.Add(k.lines, linesList); replyDict.Add(k.lineCount, linesList.Count); return replyDict; @@ -581,7 +581,7 @@ private ProductionLine DecalibrateLine(ProductionInProgress productionInProgress { cprg = null; - var productionLine = ProductionLine.LoadByProductionId(productionInProgress.character, productionInProgress.ID); + var productionLine = ProductionLine.LoadByProductionId(productionInProgress.Character, productionInProgress.ID); if (productionLine == null) { Logger.Error("DecalibrateLine: productionline was not found. a production in progress exists without related productionline. " + productionInProgress); @@ -632,17 +632,17 @@ private ProductionLine DecalibrateLine(ProductionInProgress productionInProgress var info = new Dictionary { - {k.facility, GetFacilityInfo(productionInProgress.character)}, + {k.facility, GetFacilityInfo(productionInProgress.Character)}, {k.line, productionLine.ToDictionary()} }; Message.Builder.SetCommand(Commands.ProductionLineDead) .WithData(info) - .ToCharacter(productionInProgress.character) + .ToCharacter(productionInProgress.Character) .Send(); } - ProductionLine.PostMassProduction(productionInProgress.character, productionLine.Id, newTimeEfficiency, newMaterialEfficiency); + ProductionLine.PostMassProduction(productionInProgress.Character, productionLine.Id, newTimeEfficiency, newMaterialEfficiency); return productionLine; } diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacilityEx.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacilityEx.cs index 085279f34..577053fa3 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacilityEx.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacilityEx.cs @@ -36,9 +36,9 @@ private static void SetPauseInFacility(ProductionFacility facility, bool isPause { try { - var productions = facility.ProductionProcessor.RunningProductions.Where(p => p.facilityEID == facility.Eid).ToArray(); + var productions = facility.ProductionProcessor.RunningProductions.Where(p => p.FacilityEID == facility.Eid).ToArray(); - foreach (var productionInProgressGroup in productions.GroupBy(p=>p.character)) + foreach (var productionInProgressGroup in productions.GroupBy(p=>p.Character)) { var currentCharacter = productionInProgressGroup.Key; diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs index 1874c70d8..dcd16b432 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs @@ -111,18 +111,18 @@ public ProductionInProgress StartPrototype(Character character, ProductionDescri prototypeTimeSeconds = GetShortenedProductionTime(prototypeTimeSeconds); var productionInProgress = ProductionInProgressFactory(); - productionInProgress.amountOfCycles = 1; - productionInProgress.baseEID = Parent; - productionInProgress.character = character; - productionInProgress.facilityEID = Eid; - productionInProgress.finishTime = DateTime.Now.AddSeconds(prototypeTimeSeconds); - productionInProgress.pricePerSecond = GetPricePerSecond(productionDescription.GetPrototypeDefinition()); + productionInProgress.AmountOfCycles = 1; + productionInProgress.BaseEID = Parent; + productionInProgress.Character = character; + productionInProgress.FacilityEID = Eid; + productionInProgress.FinishTime = DateTime.Now.AddSeconds(prototypeTimeSeconds); + productionInProgress.PricePerSecond = GetPricePerSecond(productionDescription.GetPrototypeDefinition()); productionInProgress.ReservedEids = reservedEids; - productionInProgress.resultDefinition = productionDescription.GetPrototypeDefinition(); - productionInProgress.startTime = DateTime.Now; - productionInProgress.totalProductionTimeSeconds = prototypeTimeSeconds; - productionInProgress.type = ProductionInProgressType.prototype; - productionInProgress.useCorporationWallet = useCorporationWallet; + productionInProgress.ResultDefinition = productionDescription.GetPrototypeDefinition(); + productionInProgress.StartTime = DateTime.Now; + productionInProgress.TotalProductionTimeSeconds = prototypeTimeSeconds; + productionInProgress.Type = ProductionInProgressType.prototype; + productionInProgress.UseCorporationWallet = useCorporationWallet; if (!productionInProgress.TryWithdrawCredit()) { @@ -211,31 +211,31 @@ private IDictionary EndPrototype(ProductionInProgress productionI //delete the used items foreach (var item in productionInProgress.GetReservedItems()) { - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.PrototypeDeleted).SetCharacter(productionInProgress.character).SetItem(item); - productionInProgress.character.LogTransaction(b); + var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.PrototypeDeleted).SetCharacter(productionInProgress.Character).SetItem(item); + productionInProgress.Character.LogTransaction(b); Repository.Delete(item); } //pick the output defintion--------------------------------------------------- - var outputDefinition = productionInProgress.resultDefinition; + var outputDefinition = productionInProgress.ResultDefinition; //load container var container = (PublicContainer) Container.GetOrThrow(PublicContainerEid); - container.ReloadItems(productionInProgress.character); + container.ReloadItems(productionInProgress.Character); var outputDefault = EntityDefault.Get(outputDefinition).ThrowIfEqual(EntityDefault.None, ErrorCodes.DefinitionNotSupported); //create item var resultItem = container.CreateAndAddItem(outputDefinition, false, item => { - item.Owner = productionInProgress.character.Eid; - item.Quantity = outputDefault.Quantity*productionInProgress.amountOfCycles; + item.Owner = productionInProgress.Character.Eid; + item.Quantity = outputDefault.Quantity*productionInProgress.AmountOfCycles; }); container.Save(); - productionInProgress.character.WriteItemTransactionLog(TransactionType.PrototypeCreated, resultItem); + productionInProgress.Character.WriteItemTransactionLog(TransactionType.PrototypeCreated, resultItem); //get list in order to return @@ -247,7 +247,7 @@ private IDictionary EndPrototype(ProductionInProgress productionI }; - ProductionProcessor.EnqueueProductionMissionTarget(MissionTargetType.prototype, productionInProgress.character,MyMissionLocationId(), productionInProgress.resultDefinition); + ProductionProcessor.EnqueueProductionMissionTarget(MissionTargetType.prototype, productionInProgress.Character,MyMissionLocationId(), productionInProgress.ResultDefinition); return replyDict; } diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs index d9b9c3da0..b0ba167d4 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ResearchLab.cs @@ -183,18 +183,18 @@ public ErrorCodes StartResearch(Character character, int researchTimeSeconds, It MoveItemsToStorage(itemsList); newProduction = ProductionInProgressFactory(); - newProduction.startTime = DateTime.Now; - newProduction.finishTime = DateTime.Now.AddSeconds(researchTimeSeconds); - newProduction.type = ProductionInProgressType.research; - newProduction.character = character; - newProduction.facilityEID = Eid; - newProduction.resultDefinition = cprgDefiniton; - newProduction.totalProductionTimeSeconds = researchTimeSeconds; - newProduction.baseEID = Parent; - newProduction.pricePerSecond = GetPricePerSecond(sourceItem.Definition); + newProduction.StartTime = DateTime.Now; + newProduction.FinishTime = DateTime.Now.AddSeconds(researchTimeSeconds); + newProduction.Type = ProductionInProgressType.research; + newProduction.Character = character; + newProduction.FacilityEID = Eid; + newProduction.ResultDefinition = cprgDefiniton; + newProduction.TotalProductionTimeSeconds = researchTimeSeconds; + newProduction.BaseEID = Parent; + newProduction.PricePerSecond = GetPricePerSecond(sourceItem.Definition); newProduction.ReservedEids = (from i in itemsList select i.Eid).ToArray(); - newProduction.useCorporationWallet = useCorporationWallet; - newProduction.amountOfCycles = 1; + newProduction.UseCorporationWallet = useCorporationWallet; + newProduction.AmountOfCycles = 1; if (!newProduction.TryWithdrawCredit()) { @@ -260,13 +260,13 @@ private IDictionary EndResearch(ProductionInProgress productionIn int researchTime; int levelDifferenceBonusPoints; - CalculateFinalResearchTimeSeconds(productionInProgress.character, itemLevel, researchKitLevel, isPrototypeItem, out researchTime, out levelDifferenceBonusPoints); + CalculateFinalResearchTimeSeconds(productionInProgress.Character, itemLevel, researchKitLevel, isPrototypeItem, out researchTime, out levelDifferenceBonusPoints); - var outputDefinition = productionInProgress.resultDefinition; + var outputDefinition = productionInProgress.ResultDefinition; //load public container var targetContainer = (PublicContainer) Container.GetOrThrow(PublicContainerEid); - targetContainer.ReloadItems(productionInProgress.character); + targetContainer.ReloadItems(productionInProgress.Character); var outputDefault = EntityDefault.Get(outputDefinition).ThrowIfEqual(EntityDefault.None,ErrorCodes.DefinitionNotSupported); (outputDefault.CategoryFlags.IsCategory(CategoryFlags.cf_calibration_programs) || outputDefault.CategoryFlags.IsCategory(CategoryFlags.cf_random_calibration_programs)).ThrowIfFalse(ErrorCodes.WTFErrorMedicalAttentionSuggested); @@ -275,7 +275,7 @@ private IDictionary EndResearch(ProductionInProgress productionIn //create item var resultItem = targetContainer.CreateAndAddItem(outputDefinition, false, item1 => { - item1.Owner = productionInProgress.character.Eid; + item1.Owner = productionInProgress.Character.Eid; item1.Quantity = 1; }); @@ -291,7 +291,7 @@ private IDictionary EndResearch(ProductionInProgress productionIn var rawMatEff = materialEfficiency; //modify the results even further - CalculateMaterialAndTimeEfficiency(productionInProgress.character, itemResearchLevel, levelDifferenceBonusPoints, ref materialEfficiency, ref timeEfficiency); + CalculateMaterialAndTimeEfficiency(productionInProgress.Character, itemResearchLevel, levelDifferenceBonusPoints, ref materialEfficiency, ref timeEfficiency); if (calibrationProgram.IsMissionRelated) { @@ -310,11 +310,11 @@ private IDictionary EndResearch(ProductionInProgress productionIn var randomCalibrationProgram = calibrationProgram as RandomCalibrationProgram; //for random missions look up for targets, gang and stuff - randomCalibrationProgram?.SetComponentsFromRunningTargets(productionInProgress.character); + randomCalibrationProgram?.SetComponentsFromRunningTargets(productionInProgress.Character); calibrationProgram.Save(); - productionInProgress.character.WriteItemTransactionLog(TransactionType.ResearchCreated, calibrationProgram); + productionInProgress.Character.WriteItemTransactionLog(TransactionType.ResearchCreated, calibrationProgram); //delete the used items @@ -322,8 +322,8 @@ private IDictionary EndResearch(ProductionInProgress productionIn Repository.Delete(researchKit); - productionInProgress.character.WriteItemTransactionLog(TransactionType.ResearchDeleted, item); - productionInProgress.character.WriteItemTransactionLog(TransactionType.ResearchDeleted, researchKit); + productionInProgress.Character.WriteItemTransactionLog(TransactionType.ResearchDeleted, item); + productionInProgress.Character.WriteItemTransactionLog(TransactionType.ResearchDeleted, researchKit); targetContainer.Save(); @@ -335,7 +335,7 @@ private IDictionary EndResearch(ProductionInProgress productionIn }; - ProductionProcessor.EnqueueProductionMissionTarget(MissionTargetType.research, productionInProgress.character, MyMissionLocationId(), calibrationProgram.Definition); + ProductionProcessor.EnqueueProductionMissionTarget(MissionTargetType.research, productionInProgress.Character, MyMissionLocationId(), calibrationProgram.Definition); return replyDict; } diff --git a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs index 79754aab9..aff99bb47 100644 --- a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs +++ b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs @@ -697,18 +697,18 @@ public ProductionInProgress StartCPRGForge(Character character, long sourceEid, forgeTimeSeconds = GetShortenedProductionTime(forgeTimeSeconds); var productionInProgress = ProductionInProgressFactory(); - productionInProgress.amountOfCycles = 1; - productionInProgress.baseEID = Parent; - productionInProgress.character = character; - productionInProgress.facilityEID = Eid; - productionInProgress.finishTime = DateTime.Now.AddSeconds(forgeTimeSeconds); - productionInProgress.pricePerSecond = GetPricePerSecond(targetDefinition); + productionInProgress.AmountOfCycles = 1; + productionInProgress.BaseEID = Parent; + productionInProgress.Character = character; + productionInProgress.FacilityEID = Eid; + productionInProgress.FinishTime = DateTime.Now.AddSeconds(forgeTimeSeconds); + productionInProgress.PricePerSecond = GetPricePerSecond(targetDefinition); productionInProgress.ReservedEids = reservedEids; - productionInProgress.resultDefinition = targetDefinition; - productionInProgress.startTime = DateTime.Now; - productionInProgress.totalProductionTimeSeconds = forgeTimeSeconds; - productionInProgress.type = ProductionInProgressType.calibrationProgramForge; - productionInProgress.useCorporationWallet = useCorporationWallet; + productionInProgress.ResultDefinition = targetDefinition; + productionInProgress.StartTime = DateTime.Now; + productionInProgress.TotalProductionTimeSeconds = forgeTimeSeconds; + productionInProgress.Type = ProductionInProgressType.calibrationProgramForge; + productionInProgress.UseCorporationWallet = useCorporationWallet; sourceCalibration.Save(); targetCalibration.Save(); @@ -762,29 +762,29 @@ private IDictionary EndCalibrationProgramForge(ProductionInProgre var targetCPRG = (CalibrationProgram)Repository.LoadOrThrow(targetCPRGEid); //delete the used items - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.CPRGForgeDeleted).SetCharacter(productionInProgress.character); + var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.CPRGForgeDeleted).SetCharacter(productionInProgress.Character); foreach (var item in productionInProgress.GetReservedItems()) { b.SetItem(item); - productionInProgress.character.LogTransaction(b.Build()); + productionInProgress.Character.LogTransaction(b.Build()); Repository.Delete(item); } //pick the output defintion--------------------------------------------------- - var outputDefinition = productionInProgress.resultDefinition; + var outputDefinition = productionInProgress.ResultDefinition; //load container var container = (PublicContainer) Container.GetOrThrow(PublicContainerEid); - container.ReloadItems(productionInProgress.character); + container.ReloadItems(productionInProgress.Character); var resultItem = (CalibrationProgram) Factory.CreateWithRandomEID(outputDefinition); - resultItem.Owner = productionInProgress.character.Eid; + resultItem.Owner = productionInProgress.Character.Eid; resultItem.Save(); int materialEfficiencyPoints; int timeEfficiencyPoints; - CalculateResultingPoints(sourceCPRG, targetCPRG, productionInProgress.character, out materialEfficiencyPoints, out timeEfficiencyPoints); + CalculateResultingPoints(sourceCPRG, targetCPRG, productionInProgress.Character, out materialEfficiencyPoints, out timeEfficiencyPoints); resultItem.MaterialEfficiencyPoints = materialEfficiencyPoints; resultItem.TimeEfficiencyPoints = timeEfficiencyPoints; @@ -793,7 +793,7 @@ private IDictionary EndCalibrationProgramForge(ProductionInProgre container.AddItem(resultItem, false); container.Save(); - productionInProgress.character.WriteItemTransactionLog(TransactionType.PrototypeCreated, resultItem); + productionInProgress.Character.WriteItemTransactionLog(TransactionType.PrototypeCreated, resultItem); //get list in order to return diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionInProgress.cs b/src/Perpetuum/Services/ProductionEngine/ProductionInProgress.cs index b30998209..8ba5d8593 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; @@ -31,22 +32,22 @@ public static IEnumerable GetByCorporation(this IEnumerabl public static IEnumerable GetCorporationPaidProductionsByFacililtyAndCharacter(this IEnumerable productions,Character character, long facilityEid) { - return productions.GetRunningProductionsByFacilityAndCharacter(character, facilityEid).Where(pip => pip.useCorporationWallet); + return productions.GetRunningProductionsByFacilityAndCharacter(character, facilityEid).Where(pip => pip.UseCorporationWallet); } public static IEnumerable GetRunningProductionsByFacilityAndCharacter(this IEnumerable productions, Character character, long facilityEid) { - return productions.GetByCharacter(character).Where(pip => pip.facilityEID == facilityEid); + return productions.GetByCharacter(character).Where(pip => pip.FacilityEID == facilityEid); } public static IEnumerable GetCorporationPaidProductionsByCharacter(this IEnumerable productions,Character character) { - return productions.GetByCharacter(character).Where(pip => pip.useCorporationWallet); + return productions.GetByCharacter(character).Where(pip => pip.UseCorporationWallet); } public static IEnumerable GetByCharacter(this IEnumerable productions, Character character) { - return productions.Where(pip => pip.character == character); + return productions.Where(pip => pip.Character == character); } } @@ -96,66 +97,82 @@ private ProductionInProgress CreateProductionInProgressFromRecord(IDataRecord re { var pip = _pipFactory(); pip.ID = record.GetValue("id"); - pip.character = Character.Get(record.GetValue("characterID")); - pip.resultDefinition = record.GetValue("resultDefinition"); - pip.type = (ProductionInProgressType) record.GetValue("type"); - pip.startTime = record.GetValue("startTime"); - pip.finishTime = record.GetValue("finishTime"); - pip.facilityEID = record.GetValue("facilityEID"); - pip.totalProductionTimeSeconds = record.GetValue("totalProductionTime"); - pip.baseEID = record.GetValue("baseEID"); - pip.creditTaken = record.GetValue("creditTaken"); - pip.pricePerSecond = record.GetValue("pricePerSecond"); - pip.useCorporationWallet = record.GetValue("useCorporationWallet"); - pip.amountOfCycles = record.GetValue("amountOfCycles"); - pip.paused = record.GetValue("paused"); - pip.pauseTime = record.GetValue("pausetime"); + pip.Character = Character.Get(record.GetValue("characterID")); + pip.ResultDefinition = record.GetValue("resultDefinition"); + pip.Type = (ProductionInProgressType) record.GetValue("type"); + pip.StartTime = record.GetValue("startTime"); + pip.FinishTime = record.GetValue("finishTime"); + pip.FacilityEID = record.GetValue("facilityEID"); + pip.TotalProductionTimeSeconds = record.GetValue("totalProductionTime"); + pip.BaseEID = record.GetValue("baseEID"); + pip.CreditTaken = record.GetValue("creditTaken"); + pip.PricePerSecond = record.GetValue("pricePerSecond"); + pip.UseCorporationWallet = record.GetValue("useCorporationWallet"); + pip.AmountOfCycles = record.GetValue("amountOfCycles"); + pip.Paused = record.GetValue("paused"); + pip.PauseTime = record.GetValue("pausetime"); return pip; } } public class ProductionInProgress { - private readonly ItemHelper _itemHelper; - private readonly DockingBaseHelper _dockingBaseHelper; - public int ID; - public Character character; - public int resultDefinition; - public ProductionInProgressType type; - public DateTime startTime; - public DateTime finishTime; - public long facilityEID; - public int totalProductionTimeSeconds; - public long baseEID; - public double creditTaken; - public double pricePerSecond; - public int amountOfCycles; - public bool useCorporationWallet; - public bool paused; - public DateTime? pauseTime; - - public long[] ReservedEids; - - public TimeSpan TotalProductionTime => TimeSpan.FromSeconds(totalProductionTimeSeconds); + private readonly ItemHelper itemHelper; + + private readonly DockingBaseHelper dockingBaseHelper; + + public int ID { get; set; } + + public Character Character { get; set; } + + public int ResultDefinition { get; set; } + + public ProductionInProgressType Type { get; set; } + + public DateTime StartTime { get; set; } + + public DateTime FinishTime { get; set; } + + public long FacilityEID { get; set; } + + public int TotalProductionTimeSeconds { get; set; } + + public long BaseEID { get; set; } + + public double CreditTaken { get; set; } + + public double PricePerSecond { get; set; } + + public int AmountOfCycles { get; set; } + + public bool UseCorporationWallet { get; set; } + + public bool Paused { get; set; } + + public DateTime? PauseTime { get; set; } + + public long[] ReservedEids { get; set; } + + public TimeSpan TotalProductionTime => TimeSpan.FromSeconds(TotalProductionTimeSeconds); public delegate ProductionInProgress Factory(); public ProductionInProgress(ItemHelper itemHelper,DockingBaseHelper dockingBaseHelper) { - _itemHelper = itemHelper; - _dockingBaseHelper = dockingBaseHelper; + this.itemHelper = itemHelper; + this.dockingBaseHelper = dockingBaseHelper; } public void WriteLog() { - ProductionHelper.ProductionLogInsert(character, resultDefinition, GetResultingAmount(), type, CalculateDurationSeconds(), Price, useCorporationWallet); + ProductionHelper.ProductionLogInsert(Character, ResultDefinition, GetResultingAmount(), Type, CalculateDurationSeconds(), Price, UseCorporationWallet); } public IEnumerable GetReservedItems() { foreach (var reservedEid in ReservedEids) { - var item = _itemHelper.LoadItem(reservedEid); + var item = itemHelper.LoadItem(reservedEid); if (item != null) yield return item; } @@ -172,20 +189,18 @@ public void LoadReservedItems() private int CalculateDurationSeconds() { - return (int)finishTime.Subtract(startTime).TotalSeconds; + return (int)FinishTime.Subtract(StartTime).TotalSeconds; } private int GetResultingAmount() { - - EntityDefault ed; - if (!EntityDefault.TryGet(resultDefinition, out ed)) + if (!EntityDefault.TryGet(ResultDefinition, out EntityDefault ed)) { - Logger.Error("consistency error! definition was not found for productioninprogress withdrawcredit. definition: " + resultDefinition); + Logger.Error("consistency error! definition was not found for productioninprogress withdrawcredit. definition: " + ResultDefinition); } - var resultingAmount = amountOfCycles * ed.Quantity; + var resultingAmount = AmountOfCycles * ed.Quantity; return resultingAmount; } @@ -200,16 +215,15 @@ private double Price return 0; } - switch (type) + switch (Type) { case ProductionInProgressType.massProduction: - return totalProductionTimeSeconds * pricePerSecond; + return TotalProductionTimeSeconds * PricePerSecond; default: - return totalProductionTimeSeconds * pricePerSecond; + return TotalProductionTimeSeconds * PricePerSecond; } - } } @@ -217,7 +231,7 @@ public bool IsMissionRelated { get { - var resultEd = EntityDefault.Get(resultDefinition); + var resultEd = EntityDefault.Get(ResultDefinition); if (resultEd.CategoryFlags.IsCategory(CategoryFlags.cf_random_items) || resultEd.CategoryFlags.IsCategory(CategoryFlags.cf_random_calibration_programs)) @@ -232,30 +246,30 @@ public bool IsMissionRelated public Dictionary ToDictionary() { - var resultQuantity = amountOfCycles; + var resultQuantity = AmountOfCycles; EntityDefault resultEd; - if (EntityDefault.TryGet(resultDefinition, out resultEd)) + if (EntityDefault.TryGet(ResultDefinition, out resultEd)) { resultQuantity *= resultEd.Quantity; } var tmpDict = new Dictionary { - {k.definition, resultDefinition}, - {k.startTime, startTime}, - {k.finishTime, finishTime}, + {k.definition, ResultDefinition}, + {k.startTime, StartTime}, + {k.finishTime, FinishTime}, {k.ID, ID}, - {k.facility, facilityEID}, - {k.productionTime, totalProductionTimeSeconds}, - {k.timeLeft, (int) finishTime.Subtract(DateTime.Now).TotalSeconds}, - {k.type, (int) type}, - {k.baseEID, baseEID}, + {k.facility, FacilityEID}, + {k.productionTime, TotalProductionTimeSeconds}, + {k.timeLeft, (int) FinishTime.Subtract(DateTime.Now).TotalSeconds}, + {k.type, (int) Type}, + {k.baseEID, BaseEID}, {k.cycle, resultQuantity}, {k.price, Price}, - {k.characterID, character.Id}, - {k.useCorporationWallet, useCorporationWallet}, - {k.paused, paused}, - {k.pauseTime, pauseTime}, + {k.characterID, Character.Id}, + {k.useCorporationWallet, UseCorporationWallet}, + {k.paused, Paused}, + {k.pauseTime, PauseTime}, }; return tmpDict; @@ -264,7 +278,7 @@ public Dictionary ToDictionary() public override string ToString() { - return string.Format("ID:{0} characterID:{1} characterEID:{2} resultDefinition:{3} {9} type:{4} facilityEID:{5} baseEID:{6} price:{7} amountOfCycles:{8}", ID, character.Id, character.Eid, resultDefinition, type, facilityEID, baseEID, Price, amountOfCycles, EntityDefault.Get(resultDefinition).Name); + return string.Format("ID:{0} characterID:{1} characterEID:{2} resultDefinition:{3} {9} type:{4} facilityEID:{5} baseEID:{6} price:{7} amountOfCycles:{8}", ID, Character.Id, Character.Eid, ResultDefinition, Type, FacilityEID, BaseEID, Price, AmountOfCycles, EntityDefault.Get(ResultDefinition).Name); } @@ -277,7 +291,7 @@ public bool TryWithdrawCredit() Logger.Info("withdrawing credit for: " + this); var transactionType = TransactionType.ProductionManufacture; - switch (type) + switch (Type) { case ProductionInProgressType.licenseCreate: case ProductionInProgressType.manufacture: @@ -285,7 +299,7 @@ public bool TryWithdrawCredit() case ProductionInProgressType.patentNofRunsDevelop: case ProductionInProgressType.patentTimeEfficiencyDevelop: { - Logger.Error("consistency error! outdated production type. " + type); + Logger.Error("consistency error! outdated production type. " + Type); throw new PerpetuumException(ErrorCodes.ServerError); } @@ -312,9 +326,9 @@ public bool TryWithdrawCredit() } //take his money - creditTaken = Price; //safety for the cancel + CreditTaken = Price; //safety for the cancel - var wallet = character.GetWallet(useCorporationWallet,transactionType); + var wallet = Character.GetWallet(UseCorporationWallet,transactionType); if (wallet.Balance < Price) return false; @@ -325,8 +339,8 @@ public bool TryWithdrawCredit() .SetTransactionType(transactionType) .SetCreditBalance(wallet.Balance) .SetCreditChange(-Price) - .SetCharacter(character) - .SetItem(resultDefinition, GetResultingAmount()); + .SetCharacter(Character) + .SetItem(ResultDefinition, GetResultingAmount()); var corpWallet = wallet as CorporationWallet; if (corpWallet != null) { @@ -335,10 +349,10 @@ public bool TryWithdrawCredit() } else { - character.LogTransaction(b); + Character.LogTransaction(b); } - _dockingBaseHelper.GetDockingBase(baseEID).AddCentralBank(transactionType, Price); + dockingBaseHelper.GetDockingBase(BaseEID).AddCentralBank(transactionType, Price); return true; } @@ -346,7 +360,7 @@ public void SetPause(bool isPaused) { if (isPaused) { - if (!paused) + if (!Paused) { // >> to paused state PauseProduction(); @@ -354,7 +368,7 @@ public void SetPause(bool isPaused) } else { - if (paused) + if (Paused) { // >> to unpause state ResumeProduction(); @@ -365,29 +379,29 @@ public void SetPause(bool isPaused) private void ResumeProduction() { - if (!paused) + if (!Paused) { Logger.Warning("production already running: " + this); return; } - paused = false; + Paused = false; - if (pauseTime == null) + if (PauseTime == null) { Logger.Error("wtf pause time is null. " + this); return; } - var pauseMoment = (DateTime) pauseTime; + var pauseMoment = (DateTime) PauseTime; - var doneBackThen = pauseMoment.Subtract(startTime); + var doneBackThen = pauseMoment.Subtract(StartTime); - startTime = DateTime.Now.Subtract(doneBackThen); - finishTime = startTime.AddSeconds(totalProductionTimeSeconds); + StartTime = DateTime.Now.Subtract(doneBackThen); + FinishTime = StartTime.AddSeconds(TotalProductionTimeSeconds); var res = - Db.Query().CommandText("update runningproduction set starttime=@startTime,finishtime=@finishTime,paused=0,pauseTime=null where id=@id").SetParameter("@startTime", startTime).SetParameter("@finishTime", finishTime).SetParameter("@id", ID) + Db.Query().CommandText("update runningproduction set starttime=@startTime,finishtime=@finishTime,paused=0,pauseTime=null where id=@id").SetParameter("@startTime", StartTime).SetParameter("@finishTime", FinishTime).SetParameter("@id", ID) .ExecuteNonQuery(); if (res != 1) @@ -398,17 +412,17 @@ private void ResumeProduction() private void PauseProduction() { - if (paused) + if (Paused) { Logger.Warning("production already paused: " + this); return; } - paused = true; - pauseTime = DateTime.Now; + Paused = true; + PauseTime = DateTime.Now; var res= - Db.Query().CommandText("update runningproduction set paused=1,pausetime=@pauseTime where id=@id").SetParameter("@pauseTime", pauseTime).SetParameter("@id", ID) + Db.Query().CommandText("update runningproduction set paused=1,pausetime=@pauseTime where id=@id").SetParameter("@pauseTime", PauseTime).SetParameter("@id", ID) .ExecuteNonQuery(); if (res != 1) @@ -419,7 +433,7 @@ private void PauseProduction() public ErrorCodes HasAccess(Character issuerCharacter) { - return character == issuerCharacter ? ErrorCodes.NoError : ErrorCodes.AccessDenied; + return Character == issuerCharacter ? ErrorCodes.NoError : ErrorCodes.AccessDenied; } /// @@ -429,7 +443,7 @@ public ErrorCodes HasAccess(Character issuerCharacter) /// private void SendProductionEventToCorporationMembers(Command command) { - if (!useCorporationWallet) + if (!UseCorporationWallet) return; var replyDict = new Dictionary {{k.production, ToDictionary()}}; @@ -437,7 +451,7 @@ private void SendProductionEventToCorporationMembers(Command command) const CorporationRole roleMask = CorporationRole.CEO | CorporationRole.DeputyCEO | CorporationRole.ProductionManager | CorporationRole.Accountant; Message.Builder.SetCommand(command) .WithData(replyDict) - .ToCorporation(character.CorporationEid, roleMask) + .ToCorporation(Character.CorporationEid, roleMask) .Send(); } @@ -452,19 +466,19 @@ public void InsertProductionInProgess() var id = Db.Query().CommandText(@"insert runningproduction (characterID,characterEID,resultDefinition,type,startTime,finishTime,facilityEID,totalProductionTime,baseEID,creditTaken,pricePerSecond,amountofcycles,useCorporationWallet) values (@characterID,@characterEID,@resultDefinition,@type,@startTime,@finishTime,@facilityEID,@totalProductionTime,@baseEID,@creditTaken,@pricePerSecond,@amountOfCycles,@useCorporationWallet); select cast(scope_identity() as int)") - .SetParameter("@characterID", character.Id) - .SetParameter("@characterEID", character.Eid) - .SetParameter("@resultDefinition", resultDefinition) - .SetParameter("@type", (int) type) - .SetParameter("@startTime", startTime) - .SetParameter("@finishTime", finishTime) - .SetParameter("@facilityEID", facilityEID) - .SetParameter("@totalProductionTime", totalProductionTimeSeconds) - .SetParameter("@baseEID", baseEID) - .SetParameter("@creditTaken", creditTaken) - .SetParameter("@pricePerSecond", pricePerSecond) - .SetParameter("@amountOfCycles", amountOfCycles) - .SetParameter("@useCorporationWallet", useCorporationWallet) + .SetParameter("@characterID", Character.Id) + .SetParameter("@characterEID", Character.Eid) + .SetParameter("@resultDefinition", ResultDefinition) + .SetParameter("@type", (int) Type) + .SetParameter("@startTime", StartTime) + .SetParameter("@finishTime", FinishTime) + .SetParameter("@facilityEID", FacilityEID) + .SetParameter("@totalProductionTime", TotalProductionTimeSeconds) + .SetParameter("@baseEID", BaseEID) + .SetParameter("@creditTaken", CreditTaken) + .SetParameter("@pricePerSecond", PricePerSecond) + .SetParameter("@amountOfCycles", AmountOfCycles) + .SetParameter("@useCorporationWallet", UseCorporationWallet) .ExecuteScalar().ThrowIfEqual(0, ErrorCodes.SQLInsertError); //save reserved EIDs diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs b/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs index 3e959e9ff..29a39020f 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionLine.cs @@ -6,7 +6,7 @@ using Perpetuum.Data; 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; @@ -16,37 +16,44 @@ namespace Perpetuum.Services.ProductionEngine public class ProductionLine { - private readonly IProductionDataAccess _productionDataAccess; - private readonly ItemHelper _itemHelper; - public int Id; - public int CharacterId; - public long FacilityEid; - public int TargetDefinition; - public double MaterialEfficiency; - public double TimeEfficiency; - public int Cycles; - public int Rounds; - public int? RunningProductionId; - public long? CPRGEid; + private readonly IProductionDataAccess productionDataAccess; + private readonly ItemHelper itemHelper; + public int Id { get; set; } + + public int CharacterId { get; set; } + + public long FacilityEid { get; set; } + + public int TargetDefinition { get; set; } + + public double MaterialEfficiency { get; set; } + + public double TimeEfficiency { get; set; } + + public int Cycles { get; set; } + + public int Rounds { get; set; } + + public int? RunningProductionId { get; set; } + + public long? CPRGEid { get; set; } public Dictionary ToDictionary() { return new Dictionary - { - {k.ID, Id}, - {k.facility, FacilityEid}, - {k.targetDefinition, TargetDefinition}, - {k.materialEfficiency,(int) MaterialEfficiency}, - {k.timeEfficiency,(int) TimeEfficiency}, - {k.cycle, Cycles}, - {k.production, RunningProductionId}, - {k.rounds, Rounds}, - {k.maxDistortion, Decalibration.distorsionMax}, - {k.dead, IsAtZero()} - - }; - + { + {k.ID, Id}, + {k.facility, FacilityEid}, + {k.targetDefinition, TargetDefinition}, + {k.materialEfficiency,(int) MaterialEfficiency}, + {k.timeEfficiency,(int) TimeEfficiency}, + {k.cycle, Cycles}, + {k.production, RunningProductionId}, + {k.rounds, Rounds}, + {k.maxDistortion, Decalibration.distorsionMax}, + {k.dead, IsAtZero()} + }; } public override string ToString() @@ -58,10 +65,10 @@ public override string ToString() public static Factory ProductionLineFactory { get; set; } - public ProductionLine(IProductionDataAccess productionDataAccess,ItemHelper itemHelper) + public ProductionLine(IProductionDataAccess productionDataAccess, ItemHelper itemHelper) { - _productionDataAccess = productionDataAccess; - _itemHelper = itemHelper; + this.productionDataAccess = productionDataAccess; + this.itemHelper = itemHelper; } /* @@ -93,11 +100,10 @@ public static ProductionLine CreateFromRecord(IDataRecord record) line.RunningProductionId = record.GetValue("runningproductionid"); line.Rounds = record.GetValue("rounds"); line.CPRGEid = record.GetValue("cprgeid"); + return line; } - - public bool IsAtZero() { return (MaterialEfficiency <= 0 && TimeEfficiency <= 0); @@ -136,16 +142,6 @@ public int GetTimePoints() return (int) TimeEfficiency; } - private int GetMaterialEfficiencyPercentage() - { - return (int)(MaterialEfficiency * 100); - } - - private int GetTimeEfficiencyPercentage() - { - return (int)(TimeEfficiency * 100); - } - public Character GetOwnerCharacter { get @@ -154,11 +150,11 @@ public Character GetOwnerCharacter if (character == Character.None) { Logger.Error("character not found related to productionline " + this); + throw new PerpetuumException(ErrorCodes.CharacterNotFound); } return character; - } } @@ -179,13 +175,14 @@ public static void GetDecalibratedEfficiencies(double oldMaterialEfficiency, dou newTimeEfficiency = newTimeEfficiency.Clamp(0, oldTimeEfficiency); } - public ProductionDecalibration Decalibration => _productionDataAccess.GetDecalibration(TargetDefinition); + public ProductionDecalibration Decalibration => productionDataAccess.GetDecalibration(TargetDefinition); //------ static stuff public static Dictionary ListCalibratedLines(int characterId, long facilityEid) { var counter = 0; + return (GetLinesByCharacter(characterId, facilityEid) .Select(pl => (object)pl.ToDictionary())) @@ -204,34 +201,38 @@ public static IEnumerable GetLinesByCharacter(int characterId, l public static IEnumerable GetLinesByFacilityEid(long facilityEid) { - return - Db.Query().CommandText(SELECT_LINE + " where facilityeid=@facilityEID") - .SetParameter("@facilityEID", facilityEid) - .Execute() - .Select(CreateFromRecord); - + return Db.Query() + .CommandText(SELECT_LINE + " where facilityeid=@facilityEID") + .SetParameter("@facilityEID", facilityEid) + .Execute() + .Select(CreateFromRecord); } public static int CountLinesForCharacter(Character character, long facilityEid) { - return Db.Query().CommandText("select count(*) from productionlines where characterid=@characterID and facilityeid=@facilityEID") - .SetParameter("@characterID", character.Id) - .SetParameter("@facilityEID", facilityEid) - .ExecuteScalar(); + return Db.Query() + .CommandText("select count(*) from productionlines where characterid=@characterID and facilityeid=@facilityEID") + .SetParameter("@characterID", character.Id) + .SetParameter("@facilityEID", facilityEid) + .ExecuteScalar(); } public static int DeleteAllByFacilityEid(long facilityEid) { - return Db.Query().CommandText("delete productionlines where facilityeid=@facilityEid").SetParameter("@facilityEid", facilityEid).ExecuteNonQuery(); + return Db.Query() + .CommandText("delete productionlines where facilityeid=@facilityEid") + .SetParameter("@facilityEid", facilityEid) + .ExecuteNonQuery(); } public static ErrorCodes LoadById(int id, out ProductionLine productionLine) { productionLine = null; - var record = - Db.Query().CommandText(SELECT_LINE + " where id=@ID").SetParameter("@ID", id) + var record = Db.Query() + .CommandText(SELECT_LINE + " where id=@ID") + .SetParameter("@ID", id) .ExecuteSingleRow(); if (record == null) @@ -240,14 +241,15 @@ public static ErrorCodes LoadById(int id, out ProductionLine productionLine) } productionLine = CreateFromRecord(record); - return ErrorCodes.NoError; + return ErrorCodes.NoError; } [NotNull] public static ProductionLine LoadByIdAndCharacterAndFacility(Character character, int lineId, long facilityEid) { - var record = Db.Query().CommandText(SELECT_LINE + " where id=@lineID and characterId=@characterID and facilityeid=@facility") + var record = Db.Query() + .CommandText(SELECT_LINE + " where id=@lineID and characterId=@characterID and facilityeid=@facility") .SetParameter("@lineID", lineId) .SetParameter("@characterId", character.Id) .SetParameter("@facility", facilityEid) @@ -258,57 +260,66 @@ public static ProductionLine LoadByIdAndCharacterAndFacility(Character character public static ErrorCodes SetRounds(int targetRounds, int lineId) { - var res = - Db.Query().CommandText("update productionlines set rounds=@rounds where id=@lineID").SetParameter("@lineID", lineId).SetParameter("@rounds", targetRounds) - .ExecuteNonQuery(); - - - return res == 1 ? ErrorCodes.NoError : ErrorCodes.SQLUpdateError; + var res = Db.Query() + .CommandText("update productionlines set rounds=@rounds where id=@lineID") + .SetParameter("@lineID", lineId) + .SetParameter("@rounds", targetRounds) + .ExecuteNonQuery(); + return res == 1 + ? ErrorCodes.NoError + : ErrorCodes.SQLUpdateError; } - public void DecreaseRounds() { - Rounds--; - if (Rounds < 0) Rounds = 0; + if (Rounds < 0) + { + Rounds = 0; + } SetRounds(Rounds, Id); - } - - public static ErrorCodes SetRunningProductionId(int lineId, int? runningProductionId) { object rpid = DBNull.Value; if (runningProductionId != null) rpid = (int)runningProductionId; - var res = - Db.Query().CommandText("update productionlines set runningproductionid=@rpid where id=@lineID").SetParameter("@lineID", lineId).SetParameter("@rpid", rpid) + var res = Db.Query() + .CommandText("update productionlines set runningproductionid=@rpid where id=@lineID") + .SetParameter("@lineID", lineId) + .SetParameter("@rpid", rpid) .ExecuteNonQuery(); - return (res == 1) ? ErrorCodes.NoError : ErrorCodes.SQLUpdateError; - + return (res == 1) + ? ErrorCodes.NoError + : ErrorCodes.SQLUpdateError; } public static ErrorCodes CheckOwner(int characterId, int lineId) { - var res = - Db.Query().CommandText("select targetdefinition from productionlines where characterid=@characterID and id=@ID").SetParameter("@characterID", characterId).SetParameter("@ID", lineId) - .ExecuteScalar(); + var res = Db.Query() + .CommandText("select targetdefinition from productionlines where characterid=@characterID and id=@ID") + .SetParameter("@characterID", characterId) + .SetParameter("@ID", lineId) + .ExecuteScalar(); - return (res == 0) ? ErrorCodes.AccessDenied : ErrorCodes.NoError; + return (res == 0) + ? ErrorCodes.AccessDenied + : ErrorCodes.NoError; } public static void DeleteById(int lineId) { - Db.Query().CommandText("delete productionlines where id=@ID") + Db.Query() + .CommandText("delete productionlines where id=@ID") .SetParameter("@ID", lineId) - .ExecuteNonQuery().ThrowIfEqual(0,ErrorCodes.SQLDeleteError); + .ExecuteNonQuery() + .ThrowIfEqual(0,ErrorCodes.SQLDeleteError); } public static void CreateCalibratedLine(Character character, long facilityEid,CalibrationProgram program) @@ -316,38 +327,44 @@ public static void CreateCalibratedLine(Character character, long facilityEid,Ca const string insertSqlCommand = @"INSERT dbo.productionlines (characterid,facilityeid,targetdefinition,materialefficiency,timeefficiency,cprgeid) VALUES (@characterID, @facility ,@definition,@materialEfficiency,@timeEfficiency,@cprgEid)"; - Db.Query().CommandText(insertSqlCommand) + Db.Query() + .CommandText(insertSqlCommand) .SetParameter("@characterID", character.Id) .SetParameter("@facility", facilityEid) .SetParameter("@definition",program.TargetDefinition) .SetParameter("@materialEfficiency",program.MaterialEfficiencyPoints) .SetParameter("@timeEfficiency",program.TimeEfficiencyPoints) .SetParameter("@cprgEid", program.Eid) - .ExecuteNonQuery().ThrowIfEqual(0,ErrorCodes.SQLInsertError); + .ExecuteNonQuery() + .ThrowIfEqual(0,ErrorCodes.SQLInsertError); } - public static void PostMassProduction(Character character, int lineId, double newTimeEfficiency, double newMaterialEfficiency) { - Db.Query().CommandText("update productionlines set runningproductionid=NULL, cycles=cycles+1,materialefficiency=@materialEfficiency,timeefficiency=@timeEfficiency where characterid=@characterID and id=@lineID") + Db.Query() + .CommandText("update productionlines set runningproductionid=NULL, cycles=cycles+1,materialefficiency=@materialEfficiency,timeefficiency=@timeEfficiency where characterid=@characterID and id=@lineID") .SetParameter("@characterID", character.Id) .SetParameter("@lineID", lineId) .SetParameter("@timeEfficiency", newTimeEfficiency) .SetParameter("@materialEfficiency", newMaterialEfficiency) - .ExecuteNonQuery().ThrowIfEqual(0,ErrorCodes.SQLUpdateError); + .ExecuteNonQuery() + .ThrowIfEqual(0,ErrorCodes.SQLUpdateError); } public static ErrorCodes LoadByProductionId(Character character, int productionInProgressId, out ProductionLine productionLine) { productionLine = null; - var record = Db.Query().CommandText(SELECT_LINE + " where characterid=@characterID and runningproductionid=@rpid") - .SetParameter("@characterID", character.Id) - .SetParameter("@rpid", productionInProgressId) - .ExecuteSingleRow(); + var record = Db.Query() + .CommandText(SELECT_LINE + " where characterid=@characterID and runningproductionid=@rpid") + .SetParameter("@characterID", character.Id) + .SetParameter("@rpid", productionInProgressId) + .ExecuteSingleRow(); - if (record == null) + if (record == null) + { return ErrorCodes.ItemNotFound; + } productionLine = CreateFromRecord(record); @@ -357,25 +374,29 @@ public static ErrorCodes LoadByProductionId(Character character, int productionI [CanBeNull] public static ProductionLine LoadByProductionId(Character character, int productionInProgressId) { - var record = Db.Query().CommandText(SELECT_LINE + " where characterid=@characterID and runningproductionid=@rpid") - .SetParameter("@characterID", character.Id) - .SetParameter("@rpid", productionInProgressId) - .ExecuteSingleRow(); + var record = Db.Query() + .CommandText(SELECT_LINE + " where characterid=@characterID and runningproductionid=@rpid") + .SetParameter("@characterID", character.Id) + .SetParameter("@rpid", productionInProgressId) + .ExecuteSingleRow(); - if (record == null) + if (record == null) + { return null; + } return CreateFromRecord(record); } - public void CalculateDecalibrationPenalty(Character character, out int newMaterialEfficiencyPoints, out int newTimeEfficiencyPoints) { - var extensionBonus = character.GetExtensionBonusByName(ExtensionNames.PRODUCTION_DECALIBRATION_EFFICIENCY).Clamp(0, 10); + var extensionBonus = character + .GetExtensionBonusByName(ExtensionNames.PRODUCTION_DECALIBRATION_EFFICIENCY) + .Clamp(0, 10); var penaltyMultiplier = 0.9 + (extensionBonus * 0.01); - newMaterialEfficiencyPoints =(int)( MaterialEfficiency * penaltyMultiplier); - newTimeEfficiencyPoints = (int) (TimeEfficiency * penaltyMultiplier); + newMaterialEfficiencyPoints = (int)(MaterialEfficiency * penaltyMultiplier); + newTimeEfficiencyPoints = (int)(TimeEfficiency * penaltyMultiplier); } public int GetCalibrationTemplateDefinition() @@ -385,8 +406,10 @@ public int GetCalibrationTemplateDefinition() return DynamicCalibrationProgram.GetDynamicTemplateDefinition(TargetDefinition); } - var prototypePairDefinition = _productionDataAccess.GetPrototypePair(TargetDefinition); - var researchLevel = _productionDataAccess.ResearchLevels.GetOrDefault(prototypePairDefinition).ThrowIfNull(ErrorCodes.ItemNotResearchable); + var prototypePairDefinition = productionDataAccess.GetPrototypePair(TargetDefinition); + var researchLevel = productionDataAccess.ResearchLevels + .GetOrDefault(prototypePairDefinition) + .ThrowIfNull(ErrorCodes.ItemNotResearchable); return (int)researchLevel.calibrationProgramDefinition.ThrowIfNull(ErrorCodes.ServerError); } @@ -394,37 +417,33 @@ public int GetCalibrationTemplateDefinition() [NotNull] public CalibrationProgram GetOrCreateCalibrationProgram(Mill mill) { - - CalibrationProgram calibrationProgram; - + CalibrationProgram calibrationProgram; - if (CPRGEid == null) - { - Logger.Info("cprg eid is creating one for " + this); + if (CPRGEid == null) + { + Logger.Info("cprg eid is creating one for " + this); - var character = GetOwnerCharacter; + var character = GetOwnerCharacter; - //create item to ram - var calibrationProgramDefinition = GetCalibrationTemplateDefinition(); - calibrationProgram = (CalibrationProgram) Entity.Factory.CreateWithRandomEID(calibrationProgramDefinition); - calibrationProgram.Owner = character.Eid; - mill.GetStorage().AddChild(calibrationProgram); + //create item to ram + var calibrationProgramDefinition = GetCalibrationTemplateDefinition(); + calibrationProgram = (CalibrationProgram) Entity.Factory.CreateWithRandomEID(calibrationProgramDefinition); + calibrationProgram.Owner = character.Eid; + mill.GetStorage().AddChild(calibrationProgram); - // db-be kell csinalni mert a dinamikus felulirja save-nel - calibrationProgram.Save(); + // db-be kell csinalni mert a dinamikus felulirja save-nel + calibrationProgram.Save(); - Logger.Info("cprg created " + calibrationProgram); - - } - else - { - //load from sql - calibrationProgram = (CalibrationProgram)_itemHelper.LoadItemOrThrow((long) CPRGEid); - Logger.Info("found and cprg loaded " + calibrationProgram); - } + Logger.Info("cprg created " + calibrationProgram); + } + else + { + //load from sql + calibrationProgram = (CalibrationProgram)itemHelper.LoadItemOrThrow((long) CPRGEid); + Logger.Info("found and cprg loaded " + calibrationProgram); + } - return calibrationProgram; - + return calibrationProgram; } /// @@ -446,9 +465,5 @@ public CalibrationProgram ExtractCalibrationProgram(Mill mill) return calibrationProgram; } - - - } - } diff --git a/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs b/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs index 109373333..2ba59c113 100644 --- a/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs +++ b/src/Perpetuum/Services/ProductionEngine/ProductionProcessor.cs @@ -72,18 +72,18 @@ private void LoadProductionsInProgress() foreach (var productionInProgress in _pipRepository.GetAll()) { #if DEBUG - if (!IsFacilityExists(productionInProgress.facilityEID)) + if (!IsFacilityExists(productionInProgress.FacilityEID)) { //facility not cached, skip loading the production //speed up server start - Logger.Info("facility is not cached, skipping production load. production ID:" + productionInProgress.ID + " facilityEID:" + productionInProgress.facilityEID); + Logger.Info("facility is not cached, skipping production load. production ID:" + productionInProgress.ID + " facilityEID:" + productionInProgress.FacilityEID); continue; } #endif productionInProgress.LoadReservedItems(); - var facility = GetFacility(productionInProgress.facilityEID); + var facility = GetFacility(productionInProgress.FacilityEID); if (facility != null) { AddToRunningProductions(productionInProgress); @@ -91,10 +91,10 @@ private void LoadProductionsInProgress() } else { - Logger.Error("facility not found, production cancelled. characterID:" + productionInProgress.character.Id + " ID:" + productionInProgress.ID + " facilityEID:" + productionInProgress.facilityEID); + Logger.Error("facility not found, production cancelled. characterID:" + productionInProgress.Character.Id + " ID:" + productionInProgress.ID + " facilityEID:" + productionInProgress.FacilityEID); try { - CancelProduction(productionInProgress.character, productionInProgress.ID); + CancelProduction(productionInProgress.Character, productionInProgress.ID); canceled++; } catch (Exception ex) @@ -186,7 +186,7 @@ public void ForceEndAllProduction() private void ForceEndProduction(ProductionFacility productionFacility) { //force end all found productions - var foundProductions = RunningProductions.Where(pip => pip.facilityEID == productionFacility.Eid).ToList(); + var foundProductions = RunningProductions.Where(pip => pip.FacilityEID == productionFacility.Eid).ToList(); EndProductions(foundProductions); } @@ -212,10 +212,10 @@ private void EndProduction(ProductionInProgress productionInProgress, bool force { Logger.Info("ending production. " + productionInProgress); - var facility = GetFacility(productionInProgress.facilityEID); + var facility = GetFacility(productionInProgress.FacilityEID); if (facility == null) { - Logger.Error("facility not found in endProduction. facility EID:" + productionInProgress.facilityEID); + Logger.Error("facility not found in endProduction. facility EID:" + productionInProgress.FacilityEID); _pipRepository.Delete(productionInProgress); RemoveFromRunningProductions(productionInProgress); @@ -237,7 +237,7 @@ private void EndProduction(ProductionInProgress productionInProgress, bool force var ep =CalculateEp(facility, productionInProgress); - productionInProgress.character.AddExtensionPointsBoostAndLog( EpForActivityType.Production, ep); + productionInProgress.Character.AddExtensionPointsBoostAndLog( EpForActivityType.Production, ep); if (replyDict != null) @@ -248,12 +248,12 @@ private void EndProduction(ProductionInProgress productionInProgress, bool force //if the player is online we report the facility load replyDict.Add(k.production, productionInProgress.ToDictionary()); - var facilityInfo = facility.GetFacilityInfo(productionInProgress.character); + var facilityInfo = facility.GetFacilityInfo(productionInProgress.Character); replyDict.Add(k.facility, facilityInfo); Message.Builder.SetCommand(Commands.ProductionFinished) .WithData(replyDict) - .ToCharacter(productionInProgress.character) + .ToCharacter(productionInProgress.Character) .Send(); }); @@ -301,7 +301,7 @@ private int CalculateEp(ProductionFacility facility, ProductionInProgress produc public int GetRunningProductionsCountByFacility(long facilityEid) { - return RunningProductions.Count(p => p.facilityEID == facilityEid); + return RunningProductions.Count(p => p.FacilityEID == facilityEid); } public Dictionary GetProductionsByFacilityAndCharacterToDictionary(Character character, long facilityEID) @@ -485,7 +485,7 @@ public void CancelProduction(Character character, int id) pip.HasAccess(character).ThrowIfError(); - var facility = GetFacility(pip.facilityEID).ThrowIfNull(ErrorCodes.ItemNotFound); + var facility = GetFacility(pip.FacilityEID).ThrowIfNull(ErrorCodes.ItemNotFound); //lehet-e cancelezni olyan facilityben ami zarva van? - lehessen @@ -511,7 +511,7 @@ public void CancelProduction(Character character, int id) Message.Builder.SetCommand(Commands.ProductionCancel) .WithData(replyDict) - .ToCharacter(pip.character) + .ToCharacter(pip.Character) .Send(); }); } @@ -577,7 +577,7 @@ private Task ProcessProductionsAsync() private void ProcessProductions() { var now = DateTime.Now; - var productionInProgress = RunningProductions.Where(p => p.finishTime < now && !p.paused).ToArray(); + var productionInProgress = RunningProductions.Where(p => p.FinishTime < now && !p.Paused).ToArray(); EndProductions(productionInProgress); } @@ -938,7 +938,7 @@ public void EnqueueProductionMissionTarget( MissionTargetType targetType, Charac public static void InformProductionEvent(ProductionInProgress productionInProgress, Command command) { - if (!productionInProgress.useCorporationWallet) + if (!productionInProgress.UseCorporationWallet) return; var replyDict = new Dictionary(); @@ -948,13 +948,13 @@ public static void InformProductionEvent(ProductionInProgress productionInProgre const CorporationRole roleMask = CorporationRole.CEO | CorporationRole.DeputyCEO | CorporationRole.ProductionManager | CorporationRole.Accountant; Message.Builder.SetCommand(command) .WithData(replyDict) - .ToCorporation(productionInProgress.character.CorporationEid, roleMask) + .ToCorporation(productionInProgress.Character.CorporationEid, roleMask) .Send(); } public void RemovePBSDockingBase(long baseEid) { - var inprogress = RunningProductions.Where(p => p.baseEID == baseEid).ToArray(); + var inprogress = RunningProductions.Where(p => p.BaseEID == baseEid).ToArray(); var removedRunningProduction = inprogress.Count(RemoveFromRunningProductions); Logger.Info(removedRunningProduction + " production in progress out of " + inprogress.Length + " were removed from production. base eid: " + baseEid); @@ -1048,7 +1048,7 @@ public IDictionary QueryCPRGForge(Character character, long sour public void AbortProductionsForOneCharacter(Character character) { - foreach (var productionInProgress in RunningProductions.Where(pip => pip.character.Equals(character))) + foreach (var productionInProgress in RunningProductions.Where(pip => pip.Character.Equals(character))) { Logger.Info("force removing production " + productionInProgress); _pipRepository.Delete(productionInProgress); diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs index 7ee370cbecf40d6ad9cb06a21fa5fd7d197e6b94..c9bcae774293d6b7bde19456a6c95021fa1d8641 100644 GIT binary patch delta 21 acmX@aeT;j<8Ae8h&8Hb9LDXZW`%C~{j443-S$44ObIHy8jcjcp@YAqEKA*{`_ I6&^DI0I@JPSpWb4 delta 158 zcmbQ>{?Bd04%W%LShXe}V3H9}XDDLGXDDSTV9;U6WXNMEVNhVGV9?nt%XXcOQDO2# z9>d8hyk1b&JYF3r>lZH{lx4vu0AX#e;4@?0yiJ&oi(LU|WD-Nx)lIP_mCtqR_nf#7PU~++i1yICYA(2sQ^IC<+OaK+eEvWzi diff --git a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs index 5db5dc04f5803c79338a2bdb696a1bab4df44c3c..c36c5c9a1c154a876c94bbbf7ac5a964cc63e01d 100644 GIT binary patch delta 74 zcmeCvo}sC$+6I&I6BM)T25<$FUJPQh!VH1}2VVeh0CxaxlYayoli&pylYkF4 Ylh6iAlaLHBlfn$Dv)&NS1Cy>4>PrL}*Z=?k diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs index ac0b90fbfa84efa9d8d302e33d73537a34147908..62f84b9820343be5eabd899f2fce65abf892b5c1 100644 GIT binary patch delta 14 VcmbOwHb-p3E>=c`%{y5WSpg?(1j+ya delta 12 TcmbOuHcM>7F4oO^SQA(QArAzr diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs index e758a49128a34b4307cfd32c3e0639dd24b8d870..b22635b76efeafd5bf2b41affabad473ca4ee488 100644 GIT binary patch delta 36 ocmeyz*TlaeicwsF!HuDmA&()M!3IcY0$C*tIt&V%a~TEM0Hx3e>;M1& delta 32 kcmZqT|Hro>ic#2&p_CzyA(_DjNM{0BB@8;73mFC30FB-UtN;K2 diff --git a/src/Perpetuum/Units/DockingBases/DockingBaseHelper.cs b/src/Perpetuum/Units/DockingBases/DockingBaseHelper.cs index 7f72b35ea..6e949a45b 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; @@ -15,29 +15,36 @@ namespace Perpetuum.Units.DockingBases { public class UnitHelper { - private readonly IZoneManager _zoneManager; - private readonly ItemHelper _itemHelper; + private readonly IZoneManager zoneManager; + private readonly ItemHelper itemHelper; public UnitHelper(IZoneManager zoneManager,ItemHelper itemHelper) { - _zoneManager = zoneManager; - _itemHelper = itemHelper; + this.zoneManager = zoneManager; + this.itemHelper = itemHelper; } public T GetUnitOrThrow(long unitEid) where T : Unit { var unit = GetUnit(unitEid); + if (unit == null) + { throw new PerpetuumException(ErrorCodes.ItemNotFound); + } + return unit; } public T GetUnit(long unitEid) where T : Unit { if (unitEid == 0) + { return null; + } + + var unit = zoneManager.GetUnit(unitEid); - var unit = _zoneManager.GetUnit(unitEid); if (unit == null) { Logger.DebugWarning($"{typeof(T).Name} not found on zone! eid:{unitEid}"); @@ -48,47 +55,47 @@ public T GetUnit(long unitEid) where T : Unit public Unit LoadUnit(long unitEid) { - return (Unit) _itemHelper.LoadItem(unitEid); + return (Unit)itemHelper.LoadItem(unitEid); } public Unit LoadUnitOrThrow(long unitEid) { - return (Unit) _itemHelper.LoadItemOrThrow(unitEid); + return (Unit)itemHelper.LoadItemOrThrow(unitEid); } public Unit CreateUnit(EntityDefault entityDefault, EntityIDGenerator idGenerator) { - return (Unit) _itemHelper.CreateItem(entityDefault, idGenerator); + return (Unit)itemHelper.CreateItem(entityDefault, idGenerator); } public Unit CreateUnit(int definition, EntityIDGenerator idGenerator) { - return (Unit) _itemHelper.CreateItem(definition, idGenerator); + return (Unit)itemHelper.CreateItem(definition, idGenerator); } } public class DockingBaseHelper { - private readonly IEntityServices _entityServices; - private readonly IZoneManager _zoneManager; - private readonly UnitHelper _unitHelper; + private readonly IEntityServices entityServices; + private readonly IZoneManager zoneManager; + private readonly UnitHelper unitHelper; public DockingBaseHelper(IEntityServices entityServices,IZoneManager zoneManager,UnitHelper unitHelper) { - _entityServices = entityServices; - _zoneManager = zoneManager; - _unitHelper = unitHelper; + this.entityServices = entityServices; + this.zoneManager = zoneManager; + this.unitHelper = unitHelper; } [CanBeNull] public DockingBase GetDockingBase(long dockingBaseEid) { - return _unitHelper.GetUnit(dockingBaseEid); + return unitHelper.GetUnit(dockingBaseEid); } public TrainingDockingBase GetTrainingDockingBase() { - return _zoneManager.Zones.GetUnits().FirstOrDefault(); + return zoneManager.Zones.GetUnits().FirstOrDefault(); } public IEnumerable GetDefaultDockingBases() @@ -98,52 +105,58 @@ public IEnumerable GetDefaultDockingBases() public IEnumerable GetAllPublicDockingBases() { - return _zoneManager.Zones.GetUnits() + return zoneManager.Zones + .GetUnits() .Where(b => b.IsCategory(CategoryFlags.cf_public_docking_base)); } public Entity GetStationService(Entity serviceBase, EntityDefault serviceEntityDefault) { - return _entityServices.Repository.GetChildByDefinition(serviceBase, serviceEntityDefault.Definition); + return entityServices.Repository.GetChildByDefinition(serviceBase, serviceEntityDefault.Definition); } [CanBeNull] public PublicContainer GetPublicContainer(Entity dockingBase) { - return (PublicContainer) GetStationService(dockingBase, _entityServices.Defaults.GetByName(DefinitionNames.PUBLIC_CONTAINER)); + return (PublicContainer)GetStationService( + dockingBase, + entityServices.Defaults.GetByName(DefinitionNames.PUBLIC_CONTAINER)); } [CanBeNull] public Market GetMarket(Entity dockingBase) { - var market = _entityServices.Defaults.GetByName(DefinitionNames.PUBLIC_MARKET); + var market = entityServices.Defaults.GetByName(DefinitionNames.PUBLIC_MARKET); + return (Market)GetStationService(dockingBase,market); } [CanBeNull] public ItemShop GetItemShop(Entity dockingBase) { - var itemShop = _entityServices.Defaults.GetByName(DefinitionNames.BASE_ITEM_SHOP); + var itemShop = entityServices.Defaults.GetByName(DefinitionNames.BASE_ITEM_SHOP); + return (ItemShop)GetStationService(dockingBase,itemShop); } [CanBeNull] public PublicCorporationHangarStorage GetPublicCorporationHangarStorage(Entity dockingBase) { - var storage = _entityServices.Defaults.GetByName(DefinitionNames.PUBLIC_CORPORATE_HANGARS_STORAGE); + var storage = entityServices.Defaults.GetByName(DefinitionNames.PUBLIC_CORPORATE_HANGARS_STORAGE); + return (PublicCorporationHangarStorage)GetStationService(dockingBase,storage); } public IEnumerable GetProductionFacilities(Entity dockingBase) { - var childrenEid = _entityServices.Repository.GetFirstLevelChildrenByCategoryflags(dockingBase.Eid, CategoryFlags.cf_production_facilities); + var childrenEid = entityServices.Repository.GetFirstLevelChildrenByCategoryflags( + dockingBase.Eid, + CategoryFlags.cf_production_facilities); foreach (var facilityEid in childrenEid) { - yield return (ProductionFacility)_entityServices.Repository.LoadOrThrow(facilityEid); + yield return (ProductionFacility)entityServices.Repository.LoadOrThrow(facilityEid); } } - - } } \ No newline at end of file diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs index fc0bae2ee34046344e131e1eb38cfd8ba552dade..6c95262eed9857554146cad201d085248aa367ad 100644 GIT binary patch delta 44 zcmX>mwoPn<4f|v}b~%0phBAgkh8%`chExVk2F=Nith$?LurFhpJc&bUavIkI00>|V AS^xk5 delta 54 zcmV-60LlNh7RDB^JO=?FlRF0=4|V`y0Bitt0A&Cv03ZM Date: Thu, 24 Aug 2023 00:52:37 +0300 Subject: [PATCH 353/457] - Removing files with wrong encoding # Conflicts: # src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs # src/Perpetuum/Services/TechTree/TechTreeService.cs --- src/Perpetuum/Converters/DelegateConverter.cs | Bin 852 -> 0 bytes src/Perpetuum/Perpetuum.csproj | 7 ------- src/Perpetuum/Services/TechTree/Points.cs | Bin 1478 -> 0 bytes src/Perpetuum/Services/TechTree/TechTreeNode.cs | Bin 9104 -> 0 bytes .../Services/TechTree/TechTreePointsHandler.cs | Bin 5528 -> 0 bytes .../Services/TechTree/TechTreeService.cs | Bin 2844 -> 0 bytes src/Perpetuum/Threading/LockFree.cs | Bin 1922 -> 0 bytes src/Perpetuum/Wallets/Wallet.cs | Bin 2870 -> 0 bytes 8 files changed, 7 deletions(-) delete mode 100644 src/Perpetuum/Converters/DelegateConverter.cs delete mode 100644 src/Perpetuum/Services/TechTree/Points.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs delete mode 100644 src/Perpetuum/Threading/LockFree.cs delete mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs deleted file mode 100644 index 227d04a85db80b19fc12c54e25df396d2eb1f37a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 852 zcmbV~-D<)>5QWdR(0AC&6p9Z}wKw%9R|4@^H!23-(HB+<{DZy`#JSHY*uXyqC?R-Pc6;tC0@3|&qI_L%YnH}Hk?=cyg zCN_I5z`lk_jl-d3$jiVlr$_GQT22c%Uiamvw^q^aW_SMXNc0aRZ5)q{$ry(2o|v_Z zkJE^rwDZ?IEz<5PRK1*nS(@x^!q?r7zJGyZM)hC4hlii3|GOpsse4M%MgHHI82Xx5 Fr7xvdek=e0 diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 0ef46e56a..ae4cb4919 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,7 +163,6 @@ - @@ -588,7 +587,6 @@ - @@ -628,7 +626,6 @@ - @@ -701,13 +698,9 @@ - - - - diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs deleted file mode 100644 index c9bcae774293d6b7bde19456a6c95021fa1d8641..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1478 zcma)5O;5r=5PfG8|HFn8B&PB1$3=cLo0| z+SiIT#1{%8l!`vXgptH!wRA9TokI+3%$pJ)8eO~-q6x=Wt|fPwGTQ3D zBZmA6@_1q{!r1XmMb9<&6;C7b5(O&Sg@?UqBbc>|l=7v=mFOvEs?>rRwy_gCThw5a$4Vx8C(Wv1n%ETfMTGh53;#IfXid{C>o|7ty626jlitEySEuC%( zc!barN2!%>*rA5o*8LIp+K)D~DSLf`>s{CTSr<)~oQOAOAX9M5^Msbz%Raj*7WIFn zq`zeTOLl70=`s9c%wn-Eb6fxCVqUgx(PQmw>TRleoT2`%QwD#XUekICgAhBcrJ#mq SUq$miwAKeEto_=bYWxCcw$`Zt diff --git a/src/Perpetuum/Services/TechTree/TechTreeNode.cs b/src/Perpetuum/Services/TechTree/TechTreeNode.cs deleted file mode 100644 index 7f07c23e70bfa4e36f39f5af229eb84e5b5fde0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9104 zcmc&)ZBHXr6h5C#{13Cln4+f9_`&$HBt}*w*_dY87>rQ@r9icm+Lm2N!(ZR^dG6`q z-WkrFnYQ4jDYr9o?|FUB%e}+4_vNbyGhXH{Qhcl z4Q`gGG3{+!#R|6x@@&10Wwe+-4xz_$*$LX@B}j2*D?KYmT0~o0A9&Pz4gQRr*7bbK zwa&|_$IVhzzcbR!&Ni0q&6pd9EWj6z_(OK|94^J7-%mIZg@IA-xIs97S zj{Uj|G3j$H$4pf-%LJVz*TRj*FTlK z5+cLb%8$`Vtw~d*+%&6+OSFg5i`PvnZ=e@-%f)Hbgc+AJEg#qu8|(0s#szCNtAtjV zs+SE*1!}cs{5cT%bI6I8_qFsD?)8E4DUjAd?Xeo0-q(s@v(~`^YtIxKdwEA~K?#k6 zb`f*=4XZ|oN=BmMeFrjbVcrF+X7naePz-#VP$p6Qi_|&7ID{3imLbbf}dFl z)n<|2KCIT>MuzHD{ByGceT#)v=-;-WPkQVer^xFCz5{uSv1%oYk&cTb=D^gWyP#67 znw%4D%PY;D%$Yt4+eSBIk&$g(7$fKwk-(^D6=Ba#epjege?v0PWT)^Ncf6IIwX(Qx zM~{A!WL`bAjA!G*tuywtIxc_%Kgwtq&s%q}SQ|Nv6FH;^$aCw3443b4*Wny-r7=sb ztTWigPRn}Tb!an2N$*hBSLVa>o%Px^Yac~QX%cVuJ|CF}=doN81aBzGPo8b5(8Jd|@6xp1G*SKQ&&@5Xd} z(=4SGQAmvH%6L!b94_xG)(ZK3d9CQ#+R*YZVR72JZjVg+v#(blu~XMx$IGwUY0yr& zYQZc!21e=U*;-lEIuQ0m>;$bowb6yh(aKVn$&5pqy*#Wa|0Z8I^LxAO$%y<;DQEgz zX~QowwzC**R}FGrD%WnS^s0$*$c;nPdFt^j@iX%hD=<&E(%!ULTVjc)5wgyi+ehzHf>;raj+u z!@R=t!#V8Cb4@)5E0(_JS%IAqOmT8hotMuoRjkHL>E)aFbRQhgfFVZBIlQlDT$|+{ zYF4KO^SNe+l;a;s-|;6f)rLyFU;Pf#vuYb&v?EW=KF9Z~`%GyFu`!RO^109nEX{N7 z;{Hr_1T=KAm4R_SMMM68+8wR>0BJ9dtciL`!P_D z{kHfl#~t_GO|`q8uSHK8Ngp!io=T_3h&S_+dfiS5?Y$oR;sPBT(P(+Zo8#ldJn`ZD zRmMO$-{&jNHgb*n%+F-&$DHnitmpX!S!agq%=f;h$Uf`sU#~+oV}0zSdmVsMUEkZz z7q-*6s7S~Rw%Qv_!+?!G#SW}cwnZVIfz#LWt$Y(O_*dYY_P|~~_x)wOZG6a!TD6lQ zo)sM>UE|a9J!*Uh@)+8&-nP@F99dP1*3i=z@ SXO*5jm7uMKEDqFOck& zAu&z6+ufOa@0@$?$IRb9QyEK7268N!?8z1WO(d7TypregOv>K2<+Tju6rWsB>IPqJ5@~cnu(}ug^wmPYzU5vi2~hkaNX|655bP zBu{WHaF;9k*ZP}520tUL`v(vwt{J|k&@jd)yEAteRuY{F-oC@A0j#B`ib!58-D{!e z&M}Lt%+#7g%%^`(vEmHByeD72ezQlKe zH$AMlnHbZSZM^FW$6n)mqewiiwH_CbN-IL#5J@9%Z$nfwS%bA+odt%|dZ1asCF47pX#BEgOOW|SENn^xe91L7n8nDUAuP>g1z#ey zgc*H2a~UU-pO8sO9O>e9qBi8w`Z7p9K-_Q1j(h}*-z)Dfz)#x5sPZ0l9^QzaxQGog zGn{R-N7LRkN^mreEPidH4lwKPD#k9KakYiw6NSFp)3d*eSH-w^?HyHZ9-c zXHv?pbx16(vk^r)qbZ$h&64t*Ya`5CQ*hC8Z8za9&*k~d$Vyh`X|rz;A6Hqhs=TZA zw5_O0PRQSf+O(#8sakuL2D6sFXYW{`8r4Um#e<7RRxGlxO3z~azi&MXF|MmJtX-^H zagCxUHs+p%wa4e14XttatsHf^nets_;9WI_<}16_$qrZQbcA@t(RE+VaaR9;tU9>U zN9?19vje!04)W)=oPl4=V9Z&pKihcA_l53{y6_Ks`#s3niMk#?&f?e*n7_I_Gyl~7 zvv(m2o}Q(+|~6(zVlg1)abb1VzM8_xI!!P;l(3PM5wpJqHr|&D zPt^g{@!i)3>tY=1AFt1;v*3S@xK6;)helj^y^r^|evh?@?AOJa1sU7}hYxl4o<|+P zKG?j4xBd);`x@?AI?3m&h&k>!7=6c@afXok3voyZtXkcKG-jr7UM9aWgL`?_ibfDa zdXH^vg4QeEI*=rg`g?L>G!5T3>PIhKWOm%_+O%Wyj~j$6S>>sV;@+;Q?o F{sB4`8two9 diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs deleted file mode 100644 index 62f84b9820343be5eabd899f2fce65abf892b5c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2844 zcmb_e+iuf95S?cv{$Z6TS3(xg5D*cmAQD1F4ZKtc1;;Ic+;E(vRh3_do-^CQw{_A+ z$V%3`J3E&%GiUwx&stV8k-1!nlXJPjESFSf63bgz$P`a13Dzc<&9NKHJFLv{%dnQn zOL;0!q+QDg)&4smccwa)_|I{Etv35I!fbReP0e%6W{_FQ5^o9KoP8^DJ-n;xW?J4b|)_&GdLhc@6A%xuB|Q=!wsww zbEkfeWUNR}k&BRx0T8)_MBSrrigRD}o-{W$p_PFR*HOe}T=Z zV%F8@bI3;W9GbqWwjnI`AWz>srvIzgdcb@PhVi^XJAHUi+&aXA7Kw7G^9DirF7nD8 zkteRSLl>xvpNbM=W2T!{j;ZjFo(t5KRf^p}9U$p*@4L?;jw-o)JgSVbxJ8adqwZ4( z?PJif{x`n%2#`0oKqHjtLzxS4r7?kx`)?MLvhfz#s`qihg&nP)!;D$&NZz1D_mP*Z z`(i|$f>DS_!7=REW{>S)`yuh}O5aQtReiKfEKzT?o$I#_x!AHX)b1$0na)a*@6q>U xPR2w|Dn}cxS+ifZ7lT9C?LDp73AEXLRkqd60aitiyAkYE*iV1Ynzw$olRN6ftSkTk diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs deleted file mode 100644 index b22635b76efeafd5bf2b41affabad473ca4ee488..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1922 zcmbW2+fKqj5QgX4#CPbG0b+OnMQ)5HCMKGI@wz~PMouXwjp5bRZ+00frLAB{ot@b^ z{Bv5rzm_tWSSB)%Kw9zvw~$E2axLd_CPlY(Y0FR&G(+ia?Y)IsO^}Yco6C$-ms23m z{7v8^{4e>9CC`Uif4ay4Hu{^P+mR75x^Rjv1(Y|EwjW0)NT77W6 z<4J2;*;SziH{(;Z7X`JXO_rV((X6dwQ)0$! zou8Z0wgxq-;X7mc$5QjFbmX$}RbArI<~JeNY}{iep$J3FY-s%+m=t|A5+eI8)IQhc z7H&*@ozY0{=zo{GhiDJr(`~A8);cVtMh|9gdrVT5G$Y-gRDPI=W~`HRcvCjNMcJ8L zvFn?afYA5mKi;w%THkHu6MprLOt;b^|3x>Ydui6?TKTtLs(I$+TJ|QVFK3IlC#0JV z{#P`Ab-PW6Lf2!htWJ*Ni{k)3YWaT$eVVH;)j LH@aVG&u!!f<1F%* diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs deleted file mode 100644 index 6c95262eed9857554146cad201d085248aa367ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2870 zcmb_eU27XL5ZvcN{)1ZbU_QX( z4)6?td*SnJTXg4Th`Dxdc0N|ICX}re=O>>nRt2-}ppgnA%z(`(RjBLKjUB&`F^k(F z)^RoE_^Dbg&xcj=h+MR$x-@Es=c^TKt47qON%e#Z_~tQErp$fFpT$|dYO7j~tILkC z&d|0Wd$o@J2kuM1o(3xjY}N8u+pXN-H;q$2-`yI?*|4gq=^AyH=j1R}z{ve+pK*9k zwal_Ti2LRHspVRJz0`Gsy(Y!_3?yoH1&al=*iTIwhd90=o_$oz9VM-TpExV$V8OGS zC;Ty%{jnxKQz_aYS@BzR(5*mgZ+(68?{`<5U%tD=8M68I!_EEvzsCCE?AO;P*Ey?H zq47eieE<`3J!pdhY-L From 158d2d8e7be02f5dc9ad329b9f0f1603cfcc4b22 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Thu, 24 Aug 2023 01:00:58 +0300 Subject: [PATCH 354/457] - Re-adding files with correct encoding --- src/Perpetuum/Converters/DelegateConverter.cs | 19 +++ src/Perpetuum/Perpetuum.csproj | 7 + src/Perpetuum/Services/TechTree/Points.cs | 28 ++++ .../Services/TechTree/TechTreeNode.cs | 139 ++++++++++++++++++ .../TechTree/TechTreePointsHandler.cs | 77 ++++++++++ .../Services/TechTree/TechTreeService.cs | 48 ++++++ src/Perpetuum/Threading/LockFree.cs | 36 +++++ src/Perpetuum/Wallets/Wallet.cs | 60 ++++++++ 8 files changed, 414 insertions(+) create mode 100644 src/Perpetuum/Converters/DelegateConverter.cs create mode 100644 src/Perpetuum/Services/TechTree/Points.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs create mode 100644 src/Perpetuum/Threading/LockFree.cs create mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs new file mode 100644 index 000000000..c16333d90 --- /dev/null +++ b/src/Perpetuum/Converters/DelegateConverter.cs @@ -0,0 +1,19 @@ +using System; + +namespace Perpetuum.Converters +{ + public class DelegateConverter : IConverter + { + private readonly Converter _converter; + + public DelegateConverter(Converter converter) + { + _converter = converter; + } + + public TOut Convert(TIn item) + { + return _converter(item); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index ae4cb4919..d4547824b 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,6 +163,7 @@ + @@ -571,6 +572,10 @@ + + + + @@ -587,6 +592,7 @@ + @@ -626,6 +632,7 @@ + diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs new file mode 100644 index 000000000..b1acd4c83 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/Points.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public struct Points + { + public static readonly Points Empty = new Points(TechTreePointType.undefined, 0); + + public readonly TechTreePointType type; + public readonly int amount; + + public Points(TechTreePointType type, int amount) + { + this.type = type; + this.amount = amount; + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"pointType",(int)type}, + {"amount",amount} + }; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeNode.cs b/src/Perpetuum/Services/TechTree/TechTreeNode.cs new file mode 100644 index 000000000..9b96151ca --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeNode.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeNode : IEquatable + { + private readonly int _definition; + private readonly int _parentDefinition; + private readonly TechTreeGroup _group; + private readonly int _x; + private readonly int _y; + + public int X => _x; + public int Definition => _definition; + public int ParentDefinition => _parentDefinition; + public TechTreeGroup Group => _group; + + public IEnumerable Prices { get; set; } + + private TechTreeNode(int definition, int parentDefinition, TechTreeGroup group, int x, int y) + { + _definition = definition; + _parentDefinition = parentDefinition; + _group = group; + _x = x; + _y = y; + } + + public static TechTreeNode CreateFromDataRecord(IDataRecord record) + { + var definition = record.GetValue("childdefinition"); + var parentDefinition = record.GetValue("parentdefinition"); + var group = record.GetValue("groupId"); + var x = record.GetValue("x"); + var y = record.GetValue("y"); + return new TechTreeNode(definition, parentDefinition, group, x, y); + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"definition", Definition}, + {"parentdefinition", ParentDefinition}, + {"groupId", (int)Group}, + {"x", X}, + {"y", _y}, + {"prices",Prices.ToDictionary("p",p => p.ToDictionary())} + }; + } + + public override string ToString() + { + return $"Definition: {Definition}, ParentDefinition: {ParentDefinition}, GroupId: {Group}, X: {X}, Y: {_y}"; + } + + public List Traverse(IDictionary nodes) + { + var r = new List(); + + var parentDefinition = ParentDefinition; + + while (parentDefinition > 0) + { + TechTreeNode parentNode; + if (!nodes.TryGetValue(parentDefinition, out parentNode)) + break; + + r.Add(parentNode); + parentDefinition = parentNode.ParentDefinition; + } + + return r; + } + + public Extension GetEnablerExtension(IDictionary groups) + { + var enablerExtensionId = groups[Group].enablerExtensionId; + return new Extension(enablerExtensionId, X + 1); + } + + public bool Equals(TechTreeNode other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return _y == other._y && Definition == other.Definition && ParentDefinition == other.ParentDefinition && Group == other.Group && X == other.X; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((TechTreeNode)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = _definition; + hashCode = (hashCode * 397) ^ _parentDefinition; + hashCode = (hashCode * 397) ^ (int)_group; + hashCode = (hashCode * 397) ^ _x; + hashCode = (hashCode * 397) ^ _y; + return hashCode; + } + } + + public static bool operator ==(TechTreeNode left, TechTreeNode right) + { + return Equals(left, right); + } + + public static bool operator !=(TechTreeNode left, TechTreeNode right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs new file mode 100644 index 000000000..5c2a928b6 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Accounting.Characters; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreePointsHandler + { + private readonly long _owner; + + public TechTreePointsHandler(Character character) : this(character.Eid) { } + + public TechTreePointsHandler(long owner) + { + _owner = owner; + } + + public void UpdatePoints(TechTreePointType pointType, Func pointsUpdater) + { + var record = Db.Query() + .CommandText("select top 1 id,amount from techtreepoints (UPDLOCK) where owner = @owner and pointType = @pointType") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .ExecuteSingleRow(); + + var id = 0; + var currentPoints = 0; + + if (record != null) + { + id = record.GetValue(0); + currentPoints = record.GetValue("amount"); + } + + var updatedPoints = pointsUpdater(currentPoints); + + if (updatedPoints < 0) + updatedPoints = 0; + + if (updatedPoints == currentPoints) + return; + + if (id > 0) + { + Db.Query().CommandText("update techtreepoints set amount = @amount where id = @id") + .SetParameter("@id", id) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); + } + else + { + Db.Query().CommandText("insert into techtreepoints (owner,pointType,amount) values (@owner,@pointType,@amount)") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + } + } + + public void AddAvailablePointsToDictionary(IDictionary dictionary, string key = "points") + { + dictionary[key] = GetAvailablePoints().ToDictionary("p", p => p.ToDictionary()); + } + + private IEnumerable GetAvailablePoints() + { + return Db.Query() + .CommandText("select pointType,amount from techtreepoints (UPDLOCK) where owner = @owner") + .SetParameter("@owner", _owner) + .Execute() + .Select(r => new Points(r.GetValue(0), r.GetValue(1))); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs new file mode 100644 index 000000000..c7ee4e801 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Data; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeService : ITechTreeService + { + private readonly ITechTreeInfoService _infoService; + + public TechTreeService(ITechTreeInfoService infoService) + { + _infoService = infoService; + } + + public void NodeUnlocked(long owner, TechTreeNode node) + { + + } + + public IEnumerable GetUnlockedNodes(long owner) + { + var nodes = _infoService.GetNodes(); + return Db.Query() + .CommandText("select definition from techtreeunlockednodes where owner = @owner") + .SetParameter("@owner", owner) + .Execute() + .Select(r => nodes[r.GetValue(0)]).ToArray(); + } + + public void AddInfoToDictionary(long owner, IDictionary dictionary) + { + dictionary["techTree"] = GetInfo(owner); + } + + private IDictionary GetInfo(long owner) + { + var info = new Dictionary + { + {"unlockedNodes", GetUnlockedNodes(owner).Select(n => n.Definition).ToArray()}, + }; + + var points = new TechTreePointsHandler(owner); + points.AddAvailablePointsToDictionary(info); + return info; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs new file mode 100644 index 000000000..622c3210f --- /dev/null +++ b/src/Perpetuum/Threading/LockFree.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; + +namespace Perpetuum.Threading +{ + public static class LockFree + { + public static void Update(ref uint destination, Func updater) + { + var spinWait = new SpinWait(); + + unsafe + { + fixed (uint* p = &destination) + { + while (true) + { + var original = *p; + var value = updater(original); + + if (Interlocked.CompareExchange(ref *(int*)p, (int)value, (int)original) == original) + return; + + spinWait.SpinOnce(); + } + } + } + } + + + public static void Swap(ref T a, ref T b) where T : class + { + Interlocked.Exchange(ref a, Interlocked.Exchange(ref b, a)); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs new file mode 100644 index 000000000..d3229dc4e --- /dev/null +++ b/src/Perpetuum/Wallets/Wallet.cs @@ -0,0 +1,60 @@ +using System.Transactions; +using Perpetuum.Data; + +namespace Perpetuum.Wallets +{ + public abstract class Wallet : IWallet + { + private TCredit _startBalance; + private bool _first = true; + + protected Wallet() + { + if (Transaction.Current != null) + Transaction.Current.OnCommited(() => OnCommited(_startBalance)); + } + + protected virtual void OnCommited(TCredit startBalance) + { + + } + + public TCredit Balance + { + get + { + var balance = GetBalance(); + + if (_first) + { + _first = false; + _startBalance = balance; + } + + return balance; + } + set + { + var current = GetBalance(); + + if (Equals(current, value)) + return; + + OnBalanceUpdating(current, value); + + SetBalance(value); + } + } + + protected abstract void SetBalance(TCredit value); + protected abstract TCredit GetBalance(); + + protected abstract void OnBalanceUpdating(TCredit currentCredit, TCredit desiredCredit); + + + public override string ToString() + { + return $"Balance: {GetBalance()}"; + } + } +} \ No newline at end of file From 08ef779c27891d6837ae0f045062d4bb6b16c711 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Thu, 24 Aug 2023 00:52:37 +0300 Subject: [PATCH 355/457] - Removing files with wrong encoding # Conflicts: # src/Perpetuum/Converters/DelegateConverter.cs # src/Perpetuum/Services/TechTree/Points.cs # src/Perpetuum/Services/TechTree/TechTreeNode.cs # src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs # src/Perpetuum/Services/TechTree/TechTreeService.cs # src/Perpetuum/Threading/LockFree.cs # src/Perpetuum/Wallets/Wallet.cs --- src/Perpetuum/Converters/DelegateConverter.cs | Bin 848 -> 0 bytes src/Perpetuum/Perpetuum.csproj | 7 ------- src/Perpetuum/Services/TechTree/Points.cs | Bin 1474 -> 0 bytes src/Perpetuum/Services/TechTree/TechTreeNode.cs | Bin 9086 -> 0 bytes .../Services/TechTree/TechTreePointsHandler.cs | Bin 5518 -> 0 bytes .../Services/TechTree/TechTreeService.cs | Bin 2842 -> 0 bytes src/Perpetuum/Threading/LockFree.cs | Bin 1918 -> 0 bytes src/Perpetuum/Wallets/Wallet.cs | Bin 2886 -> 0 bytes 8 files changed, 7 deletions(-) delete mode 100644 src/Perpetuum/Converters/DelegateConverter.cs delete mode 100644 src/Perpetuum/Services/TechTree/Points.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs delete mode 100644 src/Perpetuum/Threading/LockFree.cs delete mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs deleted file mode 100644 index 131164f216c72edccb5c345143022be8e314d734..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 848 zcmbV~&1wQM6ot>a(09mk3dIM|wj15JQqX;|(?Nt$hw%qQe0A%WOfO@`VG1QAxi`5# z=bYr{deBZQZM2lrOeeg(GOabxv!24-Xn~(`Q(&c7PE*zrKO_3USgWU@w$wR>8(*U& z!`C>6)sf{#LG+h$dZu_O)`GYl_K2LEx#Yy>Kh}HGTw{8?5=TAfRcd9eqwTRaf9sKSBB?ohotr3LqE|5l z?TXlK8yly06nU&)v9wIvFQM!G8`w?E-LN;cPJVxY LT}%vp#p|LQH_(0T diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 38a9c86aa..faa41e59d 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,7 +163,6 @@ - @@ -582,7 +581,6 @@ - @@ -622,7 +620,6 @@ - @@ -695,13 +692,9 @@ - - - - diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs deleted file mode 100644 index 7ee370cbecf40d6ad9cb06a21fa5fd7d197e6b94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1474 zcma)5+fKqj5IxT({=b^w-rhyB)iw%O#}j&g{&Y zGq>NL4c1s7$14mb*iy2{@PP#PSYgRuh8cAWN;zK>JW`YME2*2|8ke|0h%$C>1^+Ge z8;3Q*Hwq$@4t7uo4N}(a%Zk`d+rv$W^Ujv-E;S;`^4p*_T5kJ z$_?>1aL1T&fnOuc?YnK<8R0X*z2jCXTka6E4~=oVUd9;zXIRBGR@8UPjehBF+!*Vc zc#BTl8(2qq(}gzLH-hdEPr_txD1uONee zW32iJ9-<#Ae1_04#b5Pc?yp$o31n36dwh1>OZPA4if1Tc;C{%?*-Uh%D+nuLG~RlN z)WQ`XLL*}~gjXs&9l=YLJMil`^}jE0N8&12P-%J{Lr$qwdY4+MWeeBG(om1WHnd#9 zcVlnM+5NB8Ex1{rMy=QDGG4fikf-zYnxaMHu@5_5xJ_?Oeg!ekbf)FyAd6@#je#Y7 zw*s zV2x&Vkjj$7P=y*TOg{rMfA(B>d0#_cCPUdzFg{9feTx4cz_r(mlMJ6!oSv}aRQ_~fy)ZbVcN3uhBgFDvD zUMCi}{bmqh#2Coy+w~lm?`^y3 z4%m{sWyG~BSjR3)``m8QnW>bx@4C%rCUX7(kl9Uw^v%*qQC8epad(x&GNZ!r?C_=E z8~fc@Zu~mXFPW#2&0X&@)uS|Tudu^n_sAS2TG$bWjFtS{i|#x|UVa8&Sa|0?a&A76 zXtiNh=C_R!Spkbgs>}@cWX%5JzT)lo#c#pS+KQfk1&`C$O}k|JpPjwLh`qXWJr>hS z&p~|W>IV6C1dKAy(@y-Vy$HJ__JmrgN^Bu=q|<3)G}REZn1vP5-xlk3eeV|C8Ij*h zxYO23AAXhbo#tp+UC2c#t>Rkq#S+z!yI!E;(~f6RoXJb9#XRGRyVQ1Ti6x#(@Qfvn zhpve%)f7uz`0Jbr`0+;|m3_XgyUZi;Tsxw~aXd<`>x139od2i=c?Q-I zX}2<2kxzJXID?;gqA4d})gtCRCD7A>3C;}aR%y9=WPjkZp; zx>>i5O5iUAK5YR#B+foANN z;xo-V?!4P-cC%j1o)k$RGv}VlrpL%PjgmxN&k6O79{b@6of^@oJYtQp@u89Uc=1Xx zkn(-L;?QRChwcW{61{fMI@tkf*YgW1!4&+=cfKcJ&Tc!`YhTY;A&=3&4j`$i<1LGZ z?s3j*Di?Ij5}b9&7U{gy{i6jMnwBV|Ms+6 z4_e=TtZEl)Y?%#dHr3hRl{uhnCB;Wa;LK?fF z`p!XjReU>hfe4>toXu?WMx>>FN?9k?A8WC__xS!Hm*<(ULSOoS`{!A$506(+>LJYm I@zatM8gY6&$D z=ajdJIU{^$eNAcKk}DZNdx1H*bYvfB(LU2gyn2v!rq2dyPYzU5vi2~hkPF3#655bP zBu{WHaF;9kH~O1F20sJ%{T+xC*9_lNXc*#?&CK0}l|*NRw{J132WuIrB9a$N_gWaa z3(TUInObv*`Hb%=JkIdTd-CNQ?k;gp$qqweV3O<_+db`PxvkAaWa;;Gv|G^l0^bqd zbm4I`F{UkBc-Ik*y}|cZk$6~ZJuV)VR)n}Al1AR%hNxz;3TwSQhq8^d>%87vKoU{N z*ogWTgYcejQ+KW7+eFA$Sd9I7!QKMP1@ zuCO8$>J_otM2@uOBWxinufc|v)`&jx@@$d|( zcFeC^ zn4DB*nanQOOvK_GdZrbp2l?#o)reK+sMo`aL+?UOZly4i&tTapwB>hoV+}Svzr)X@ zlwIqPm|tfj%5~;by4sp02d zs7g-A-}~CMs(h*Py-I^w%h+@ESfC%(XQSnV%SQGrvam|eeEeUuBZU~(^%-_A_N}-} zF%s)j&%*BG>&?1$xkY_6A<75wu9zwN7&ACpCmv#hJCJ-ngQ_M@8+;tRnW& z!P);WWgE3~OU|L6)r<9t-DeAL`M%KIQ3tW%%6<=WcA_^9WQy^yW)`naoG75R0~QldY{t1b3$R#4X+xX)>V)uDv-q>PyPEiiFWE-bSC z`S;aN9oKzV@N2v!VBk7^E8+XY`uYFn+IGOs`{r7Cmyh?ges8sbs@K6;1-aV=dk=Nj zo=1IuA3WZ~TYq-K{S0?2+sWrE$TsdY82e+bHGN3^i43F!_Nq=o8f#KGFOy%G!96@X zL?d9(ury1mdt6HEmx-3I6Foc;8r9(8c4_6P&{ntG;O=ZyJs#+(Ng0l<()C+aMB}rM zZB%rwgn6RF^Ch0XaGe^iw~76{9;WJaHNN2r=DvH|*?JP!+sE16vKrS_K40S*^SHwD zBq!W^Ip(lp+gT)cG{zC0O)?Z*B~ZU1L+0V|odRKnk87N*dUx_1HiH9JjNXu{xVF1g4mUt0 z=T74s$yk}5q81^H0T{W0MLnXg%5(FHuTMFa0==Alovo_>D3EPiBo zq=Si1QB|*VPOE;U-8I2;uX_$1ouP^|c;~EAS90u7SJv=X?vQexTi7uMPIu8QKL6LH zoenaLb*6Ku8~O68N32X^tXiLz&hq4$+`uwHEIdKJyf@1#f)}E)@E_ljBB^$yjlPjR7y9qpdOdRfeObNZ-CHhnR& zPN64cq)-^1Y0lUV*bm8fSNg|fS=Fyri6#1t*t!1Gp%Pm)hB_U^Kc@4Nqqwkl(_Yk+mp z%IwbW{PWLg^ZQq&oeHgWqeN3ZvfC@yO7HbnZ`5}>(Y0<>ax>NZ**;pB)f(-Jvz@lA zGFFMM_*=5i!LRtO)P-TzKZEB48~bg!JJS*$89QT_^2J?-K1H|VLMW2xTQ`S ze4Sx8Z$G`mUkQ?B*}Q*eU%(*I2fj7D?vc~h=?GtSr5vj*jr$I~1t?aN_POte8TzdS zydFTZe2xxT7_-~*8QXU~dF;-Myuje4R(P_KI!r zSuk<2XO_=77?X`y8NYw5bX=3m5KYW#1)}$ENvv(Z$8&N7U56jKm_gPI`GV)W3bv zx!$dxorE_%1HPQ`py;{k<5Oz20sXB1(zE OC(85hvYF|)d(t0hmhx8s diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs deleted file mode 100644 index fc0bae2ee34046344e131e1eb38cfd8ba552dade..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2886 zcmb_eO>fgc5S=p;|6xHK5~<1$0NMiuAxt!VynaP#3a*Zz?-ZxmSq|wp zrdfPV&)=(kt$-xRWPl|zM0y9mjCKvLL0%}@8mr8$@6l}A=rgdiJ(JJyafv-{@G!I7 z;{?wXe$O?}zQk}|rqK0sv)5RqoG`RrobNSTtO{m5fFli7m;oPA&d@N(8$W(AV;;6s zsMBgH{pW1;ET3k{9dgmT?6Rnx);B5ER*R@Dvg(Ny@KJr_NSXVXKM%86wb8hoR+k@P z)39+IdXrA=N9?z{pAI{SXif65ahSQ$ZXTy`wud=VqG45&(=F<*%*knA(MM^|=8VHr zvgM}eLF|`L$Ce-;%+}p&lWjiWIp9#Yp^t$Wu*?;J-KEATQ9q+xwInHls_upAI z&d*_gV{%=h${Cv8h0ahJrSuXR=G`#fm{Mu$t$D1-P>-egeXRjUcw6L+i5}t^$636C nUg3nCgRi#of47sgeuBJ`mqlIh-uE!r3-*xr`&iD}INQq~1FU=R From 4794cdafd29b1f66ac06b5053b376f59b1994c93 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Thu, 24 Aug 2023 01:00:58 +0300 Subject: [PATCH 356/457] - Re-adding files with correct encoding --- src/Perpetuum/Converters/DelegateConverter.cs | 19 +++ src/Perpetuum/Perpetuum.csproj | 7 + src/Perpetuum/Services/TechTree/Points.cs | 28 ++++ .../Services/TechTree/TechTreeNode.cs | 139 ++++++++++++++++++ .../TechTree/TechTreePointsHandler.cs | 77 ++++++++++ .../Services/TechTree/TechTreeService.cs | 48 ++++++ src/Perpetuum/Threading/LockFree.cs | 36 +++++ src/Perpetuum/Wallets/Wallet.cs | 60 ++++++++ 8 files changed, 414 insertions(+) create mode 100644 src/Perpetuum/Converters/DelegateConverter.cs create mode 100644 src/Perpetuum/Services/TechTree/Points.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs create mode 100644 src/Perpetuum/Threading/LockFree.cs create mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs new file mode 100644 index 000000000..c16333d90 --- /dev/null +++ b/src/Perpetuum/Converters/DelegateConverter.cs @@ -0,0 +1,19 @@ +using System; + +namespace Perpetuum.Converters +{ + public class DelegateConverter : IConverter + { + private readonly Converter _converter; + + public DelegateConverter(Converter converter) + { + _converter = converter; + } + + public TOut Convert(TIn item) + { + return _converter(item); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index faa41e59d..3bea35086 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,6 +163,7 @@ + @@ -565,6 +566,10 @@ + + + + @@ -581,6 +586,7 @@ + @@ -620,6 +626,7 @@ + diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs new file mode 100644 index 000000000..b1acd4c83 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/Points.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public struct Points + { + public static readonly Points Empty = new Points(TechTreePointType.undefined, 0); + + public readonly TechTreePointType type; + public readonly int amount; + + public Points(TechTreePointType type, int amount) + { + this.type = type; + this.amount = amount; + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"pointType",(int)type}, + {"amount",amount} + }; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeNode.cs b/src/Perpetuum/Services/TechTree/TechTreeNode.cs new file mode 100644 index 000000000..9b96151ca --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeNode.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeNode : IEquatable + { + private readonly int _definition; + private readonly int _parentDefinition; + private readonly TechTreeGroup _group; + private readonly int _x; + private readonly int _y; + + public int X => _x; + public int Definition => _definition; + public int ParentDefinition => _parentDefinition; + public TechTreeGroup Group => _group; + + public IEnumerable Prices { get; set; } + + private TechTreeNode(int definition, int parentDefinition, TechTreeGroup group, int x, int y) + { + _definition = definition; + _parentDefinition = parentDefinition; + _group = group; + _x = x; + _y = y; + } + + public static TechTreeNode CreateFromDataRecord(IDataRecord record) + { + var definition = record.GetValue("childdefinition"); + var parentDefinition = record.GetValue("parentdefinition"); + var group = record.GetValue("groupId"); + var x = record.GetValue("x"); + var y = record.GetValue("y"); + return new TechTreeNode(definition, parentDefinition, group, x, y); + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"definition", Definition}, + {"parentdefinition", ParentDefinition}, + {"groupId", (int)Group}, + {"x", X}, + {"y", _y}, + {"prices",Prices.ToDictionary("p",p => p.ToDictionary())} + }; + } + + public override string ToString() + { + return $"Definition: {Definition}, ParentDefinition: {ParentDefinition}, GroupId: {Group}, X: {X}, Y: {_y}"; + } + + public List Traverse(IDictionary nodes) + { + var r = new List(); + + var parentDefinition = ParentDefinition; + + while (parentDefinition > 0) + { + TechTreeNode parentNode; + if (!nodes.TryGetValue(parentDefinition, out parentNode)) + break; + + r.Add(parentNode); + parentDefinition = parentNode.ParentDefinition; + } + + return r; + } + + public Extension GetEnablerExtension(IDictionary groups) + { + var enablerExtensionId = groups[Group].enablerExtensionId; + return new Extension(enablerExtensionId, X + 1); + } + + public bool Equals(TechTreeNode other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return _y == other._y && Definition == other.Definition && ParentDefinition == other.ParentDefinition && Group == other.Group && X == other.X; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((TechTreeNode)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = _definition; + hashCode = (hashCode * 397) ^ _parentDefinition; + hashCode = (hashCode * 397) ^ (int)_group; + hashCode = (hashCode * 397) ^ _x; + hashCode = (hashCode * 397) ^ _y; + return hashCode; + } + } + + public static bool operator ==(TechTreeNode left, TechTreeNode right) + { + return Equals(left, right); + } + + public static bool operator !=(TechTreeNode left, TechTreeNode right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs new file mode 100644 index 000000000..5c2a928b6 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Accounting.Characters; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreePointsHandler + { + private readonly long _owner; + + public TechTreePointsHandler(Character character) : this(character.Eid) { } + + public TechTreePointsHandler(long owner) + { + _owner = owner; + } + + public void UpdatePoints(TechTreePointType pointType, Func pointsUpdater) + { + var record = Db.Query() + .CommandText("select top 1 id,amount from techtreepoints (UPDLOCK) where owner = @owner and pointType = @pointType") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .ExecuteSingleRow(); + + var id = 0; + var currentPoints = 0; + + if (record != null) + { + id = record.GetValue(0); + currentPoints = record.GetValue("amount"); + } + + var updatedPoints = pointsUpdater(currentPoints); + + if (updatedPoints < 0) + updatedPoints = 0; + + if (updatedPoints == currentPoints) + return; + + if (id > 0) + { + Db.Query().CommandText("update techtreepoints set amount = @amount where id = @id") + .SetParameter("@id", id) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); + } + else + { + Db.Query().CommandText("insert into techtreepoints (owner,pointType,amount) values (@owner,@pointType,@amount)") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + } + } + + public void AddAvailablePointsToDictionary(IDictionary dictionary, string key = "points") + { + dictionary[key] = GetAvailablePoints().ToDictionary("p", p => p.ToDictionary()); + } + + private IEnumerable GetAvailablePoints() + { + return Db.Query() + .CommandText("select pointType,amount from techtreepoints (UPDLOCK) where owner = @owner") + .SetParameter("@owner", _owner) + .Execute() + .Select(r => new Points(r.GetValue(0), r.GetValue(1))); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs new file mode 100644 index 000000000..c7ee4e801 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Data; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeService : ITechTreeService + { + private readonly ITechTreeInfoService _infoService; + + public TechTreeService(ITechTreeInfoService infoService) + { + _infoService = infoService; + } + + public void NodeUnlocked(long owner, TechTreeNode node) + { + + } + + public IEnumerable GetUnlockedNodes(long owner) + { + var nodes = _infoService.GetNodes(); + return Db.Query() + .CommandText("select definition from techtreeunlockednodes where owner = @owner") + .SetParameter("@owner", owner) + .Execute() + .Select(r => nodes[r.GetValue(0)]).ToArray(); + } + + public void AddInfoToDictionary(long owner, IDictionary dictionary) + { + dictionary["techTree"] = GetInfo(owner); + } + + private IDictionary GetInfo(long owner) + { + var info = new Dictionary + { + {"unlockedNodes", GetUnlockedNodes(owner).Select(n => n.Definition).ToArray()}, + }; + + var points = new TechTreePointsHandler(owner); + points.AddAvailablePointsToDictionary(info); + return info; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs new file mode 100644 index 000000000..622c3210f --- /dev/null +++ b/src/Perpetuum/Threading/LockFree.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; + +namespace Perpetuum.Threading +{ + public static class LockFree + { + public static void Update(ref uint destination, Func updater) + { + var spinWait = new SpinWait(); + + unsafe + { + fixed (uint* p = &destination) + { + while (true) + { + var original = *p; + var value = updater(original); + + if (Interlocked.CompareExchange(ref *(int*)p, (int)value, (int)original) == original) + return; + + spinWait.SpinOnce(); + } + } + } + } + + + public static void Swap(ref T a, ref T b) where T : class + { + Interlocked.Exchange(ref a, Interlocked.Exchange(ref b, a)); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs new file mode 100644 index 000000000..d3229dc4e --- /dev/null +++ b/src/Perpetuum/Wallets/Wallet.cs @@ -0,0 +1,60 @@ +using System.Transactions; +using Perpetuum.Data; + +namespace Perpetuum.Wallets +{ + public abstract class Wallet : IWallet + { + private TCredit _startBalance; + private bool _first = true; + + protected Wallet() + { + if (Transaction.Current != null) + Transaction.Current.OnCommited(() => OnCommited(_startBalance)); + } + + protected virtual void OnCommited(TCredit startBalance) + { + + } + + public TCredit Balance + { + get + { + var balance = GetBalance(); + + if (_first) + { + _first = false; + _startBalance = balance; + } + + return balance; + } + set + { + var current = GetBalance(); + + if (Equals(current, value)) + return; + + OnBalanceUpdating(current, value); + + SetBalance(value); + } + } + + protected abstract void SetBalance(TCredit value); + protected abstract TCredit GetBalance(); + + protected abstract void OnBalanceUpdating(TCredit currentCredit, TCredit desiredCredit); + + + public override string ToString() + { + return $"Balance: {GetBalance()}"; + } + } +} \ No newline at end of file From e4a4e8da2fbb7fec3cbc1585de23797c62a65bc2 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 30 Aug 2023 09:51:48 +0300 Subject: [PATCH 357/457] RESPEC Token - Initial commit --- .../PerpetuumBootstrapper.cs | 3 + src/Perpetuum.ExportedTypes/CategoryFlags.cs | 3 + .../DefinitionNames.cs | 5 +- .../Extensions/ExtensionResetCharacter.cs | 57 +-- .../UseLotteryItem.cs | 258 +++++++---- src/Perpetuum/Accounting/AccountManager.cs | 422 ++++++++++-------- src/Perpetuum/Accounting/IAccountManager.cs | 16 +- src/Perpetuum/Items/RespecToken.cs | 34 ++ src/Perpetuum/Perpetuum.csproj | 1 + 9 files changed, 482 insertions(+), 317 deletions(-) create mode 100644 src/Perpetuum/Items/RespecToken.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 97c42a37f..095c6f226 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -921,6 +921,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -1086,6 +1087,8 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByName(DefinitionNames.FIELD_CONTAINER); ByName(DefinitionNames.MISSION_CONTAINER); ByName(DefinitionNames.ICE); + //ByName(DefinitionNames.RESPEC_TOKEN); + ByCategoryFlags(CategoryFlags.cf_respec_token); ByCategoryFlags(CategoryFlags.cf_container_capsule); ByCategoryFlags(CategoryFlags.cf_npc); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index cb9b634b7..a6787e55e 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -126,6 +126,9 @@ public enum CategoryFlags : long cf_ct_capsules_robot_thelodica_heavymech = 0x000004020303049B, cf_ct_capsules_robot_thelodica_light = 0x000001020303049B, cf_ct_capsules_robot_thelodica_mech = 0x000003020303049B, + + cf_respec_token = 0x000000000004049B, + cf_datashards = 0x0000000000000694, cf_decor = 0x000000000000005A, cf_decor_buildings = 0x000000000000055A, diff --git a/src/Perpetuum.ExportedTypes/DefinitionNames.cs b/src/Perpetuum.ExportedTypes/DefinitionNames.cs index e0a1ad26e..9b54399e8 100644 --- a/src/Perpetuum.ExportedTypes/DefinitionNames.cs +++ b/src/Perpetuum.ExportedTypes/DefinitionNames.cs @@ -1063,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"; 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/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index e0ec9e479..4cc44276d 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -14,93 +14,113 @@ namespace Perpetuum.RequestHandlers { public class UseLotteryItem : IRequestHandler { - private readonly IEntityServices _entityServices; - private readonly IAccountManager accountManager; - - public UseLotteryItem(IEntityServices entityServices, IAccountManager accountManager) + private readonly IEntityServices entityServices; + private readonly IAccountManager accountManager; + private readonly IAccountRepository accountRepository; + + public UseLotteryItem( + IEntityServices entityServices, + IAccountManager accountManager, + IAccountRepository accountRepository) { - _entityServices = entityServices; - this.accountManager = accountManager; + this.entityServices = entityServices; + this.accountManager = accountManager; + this.accountRepository = accountRepository; } - public void HandleRequest(IRequest request) { var itemEid = request.Data.GetOrDefault(k.itemEID); - var item = _entityServices.Repository.Load(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 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); + } } - 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 HandleLottery(IRequest request) + { + using (var scope = Db.CreateTransaction()) + { + var itemEid = request.Data.GetOrDefault(k.itemEID); + var containerEid = request.Data.GetOrDefault(k.containerEID); + var character = request.Session.Character; - private void HandleCalibrationTemplateItem(IRequest request, long itemEid) + var container = Container.GetWithItems(containerEid, character); + var lotteryItem = (LotteryItem)container.GetItemOrThrow(itemEid, true).Unstack(1); + + var randomEd = lotteryItem.PickRandomItem(); + var randomItem = (Item)entityServices.Factory.CreateWithRandomEID(randomEd); + randomItem.Owner = character.Eid; + + container.AddItem(randomItem, true); + entityServices.Repository.Delete(lotteryItem); + container.Save(); + + LogOpen(character, container, lotteryItem); + LogRandomItemCreated(character, container, randomItem); + + Transaction.Current.OnCommited(() => + { + var result = new Dictionary + { + {k.container,container.ToDictionary()}, + {k.item,randomItem.ToDictionary()} + }; + + 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; - character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); + var account = accountManager.Repository + .Get(request.Session.AccountId) + .ThrowIfNull(ErrorCodes.AccountNotFound); 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 containerItem = (EPBoost)container.GetItemOrThrow(itemEid, true).Unstack(1); - var ctItem = (Item)_entityServices.Factory.CreateWithRandomEID(ctDef); - ctItem.Owner = character.Eid; + containerItem.Activate(accountManager, account); - container.AddItem(ctItem, false); // CTs dont stack - _entityServices.Repository.Delete(ctCapsule); + 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, ctItem.ToDictionary() } + { k.item, boostDict } }; Message.Builder.FromRequest(request).WithData(result).Send(); }); @@ -109,36 +129,34 @@ private void HandleCalibrationTemplateItem(IRequest request, long itemEid) } } - public void HandleLottery(IRequest request) + private void HandleCalibrationTemplateItem(IRequest request, long itemEid) { using (var scope = Db.CreateTransaction()) { - var itemEid = request.Data.GetOrDefault(k.itemEID); var containerEid = request.Data.GetOrDefault(k.containerEID); var character = request.Session.Character; + character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); var container = Container.GetWithItems(containerEid, character); - var lotteryItem = (LotteryItem)container.GetItemOrThrow(itemEid, true).Unstack(1); + container.ThrowIfNotType(ErrorCodes.ContainerHasToBeOnADockingBase); //Error for unpacking elsewhere - var randomEd = lotteryItem.PickRandomItem(); - var randomItem = (Item)_entityServices.Factory.CreateWithRandomEID(randomEd); - randomItem.Owner = character.Eid; + var ctCapsule = (CalibrationProgramCapsule)container.GetItemOrThrow(itemEid, true).Unstack(1); + var ctDef = ctCapsule.Activate(); - container.AddItem(randomItem, true); - _entityServices.Repository.Delete(lotteryItem); - container.Save(); + var ctItem = (Item)entityServices.Factory.CreateWithRandomEID(ctDef); + ctItem.Owner = character.Eid; - LogOpen(character, container, lotteryItem); - LogRandomItemCreated(character, container, randomItem); + 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,randomItem.ToDictionary()} + { k.container, container.ToDictionary() }, + { k.item, ctItem.ToDictionary() } }; - Message.Builder.FromRequest(request).WithData(result).Send(); }); @@ -148,32 +166,36 @@ public void HandleLottery(IRequest request) 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) + private static void LogActivation(Character character, Container container, Item item) { - var b = TransactionLogEvent.Builder().SetTransactionType(TransactionType.LotteryRandomItemCreated) - .SetCharacter(character) - .SetContainer(container) - .SetItem(randomItem); + 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); + character.LogTransaction(b); + } private void HandlePaint(IRequest request, long paintEid) { @@ -188,7 +210,7 @@ private void HandlePaint(IRequest request, long paintEid) var paintItem = (Paint)container.GetItemOrThrow(paintEid, true).Unstack(1); paintItem.Activate(container as RobotInventory, character); - _entityServices.Repository.Delete(paintItem); + entityServices.Repository.Delete(paintItem); container.Save(); Transaction.Current.OnCommited(() => @@ -207,5 +229,65 @@ private void HandlePaint(IRequest request, long paintEid) 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); + + //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(); + } + + var relogMessage = new Dictionary + { + { k.message, "Relog required for changes to take effect" }, + { k.translate, "relog_required_message" }, + }; + + Message.Builder + .SetCommand(Commands.ServerMessage) + .WithData(relogMessage) + .ToCharacter(request.Session.Character) + .Send(); + } } } \ No newline at end of file diff --git a/src/Perpetuum/Accounting/AccountManager.cs b/src/Perpetuum/Accounting/AccountManager.cs index 4ca8ef94a..bc2548ba3 100644 --- a/src/Perpetuum/Accounting/AccountManager.cs +++ b/src/Perpetuum/Accounting/AccountManager.cs @@ -13,10 +13,13 @@ namespace Perpetuum.Accounting { public class AccountManager : IAccountManager { - private readonly AccountTransactionLogger _transactionLogger; - private readonly AccountWalletFactory _walletFactory; - private readonly EpForActivityLogger _epForActivityLogger; - private readonly EPBonusEventService _epBonusEventService; + 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, @@ -25,37 +28,39 @@ public AccountManager(IAccountRepository accountRepository, EPBonusEventService epBonusEventService) { Repository = accountRepository; - _transactionLogger = transactionLogger; - _walletFactory = walletFactory; - _epForActivityLogger = epForActivityLogger; - _epBonusEventService = epBonusEventService; + 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"), @@ -71,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; @@ -98,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 = 25; - private const double SERVER_DESIRED_EP_LEVEL = 1000000; - private const double GAURANTEED_BOOST_MAX_THRESH = 45000; - - 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, GetEpBonusFromEvent(), GetEpBonusFromSubscription(account))); + 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}, @@ -135,47 +145,13 @@ public IDictionary GetEPData(Account account,Character character) return result; } - /// - /// 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; - } - public int CalculateCurrentEp(Account account) { var dailySum = GetDailyPointsSum(account); var penaltySum = GetPenaltyPointsSum(account); var ingameSpent = GetSumExtensionPointsSpent(account); var resultEp = dailySum - penaltySum - ingameSpent; + return resultEp; } @@ -185,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; @@ -198,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; @@ -208,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(); } @@ -222,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(); @@ -244,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; @@ -265,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; @@ -289,51 +276,56 @@ 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, int multiplierBonus) + public void ExtensionSubscriptionStart(Account account, DateTime startTime, DateTime endTime, int multiplierBonus) { try { - Db.Query().CommandText("opp.extensionSubscriptionStart") + Db.Query() + .CommandText("opp.extensionSubscriptionStart") .SetParameter("@accountID", account.Id) .SetParameter("@startTime", startTime) .SetParameter("@endTime", endTime) @@ -346,78 +338,57 @@ public void ExtensionSubscriptionStart(Account account,DateTime startTime,DateTi } } - //public void ExtensionSubscriptionExtend(Account account,DateTime extendedValidUntil) - //{ - // Db.Query().CommandText("extensionSubscriptionExtend") - // .SetParameter("@accountID",account.Id) - // .SetParameter("@endTime",extendedValidUntil) - // .ExecuteNonQuery(); - //} - - /// - /// 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); } - 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); - } - public int AddExtensionPointsBoostAndLog(Account account, Character character, EpForActivityType activityType, int points) { if (points <= 0) + { return 0; + } var bonusIncrease = GetEpBonusFromEvent(); - var itemIncrease = GetEpBonusFromSubscription(account); + var itemIncrease = GetEpBonusFromSubscription(account); var rawPoints = 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, (int)BOOSTMULTIPLIERMAX, (int)bonusIncrease); + LogEpForActivity( + account, + character, + activityType, + rawPoints, + boostedPoints, + boostFactor, + (int)BOOSTMULTIPLIERMAX, + (int)bonusIncrease); }); 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(() => { @@ -425,11 +396,104 @@ public void AddExtensionPoints(Account account,int pointsToInject) }); } - private 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() { - Db.Query().CommandText("extensionPointsInject") - .SetParameter("@accountID",account.Id) - .SetParameter("@points",points) + 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) .ExecuteNonQuery(); } @@ -444,7 +508,10 @@ private int GetBoostedExtensionPoints(Account account, int points, double bonusI var dailyPointsSum = GetExtensionPointsCollected(account); var realEpGain = AddExtensionPointWithBoosting(points, dailyPointsSum, SERVER_DESIRED_EP_LEVEL, bonusIncrease, itemIncrease); if (account.IsDailyEpBoosted) + { realEpGain *= 2; + } + return realEpGain; } @@ -456,7 +523,12 @@ private int GetBoostedExtensionPoints(Account account, int points, double bonusI /// /// Any multiplier increase over the BOOSTMULTIPLIERMAX /// - private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int accoutEpSum, double epLevelThreshold, double bonusIncrease, int itemIncrease) + private static int AddExtensionPointWithBoosting( + int extensionPointsToAdd, + int accoutEpSum, + double epLevelThreshold, + double bonusIncrease, + int itemIncrease) { var result = 0; var currentEp = accoutEpSum; @@ -473,7 +545,15 @@ private static int AddExtensionPointWithBoosting(int extensionPointsToAdd, int a return Math.Max(result, extensionPointsToAdd); } - private void LogEpForActivity(Account account, Character character, EpForActivityType activityType, int rawPoints, int points, double boostFactor, int bonusMultiplier, int additionalMultiplier) + private void LogEpForActivity( + Account account, + Character character, + EpForActivityType activityType, + int rawPoints, + int points, + double boostFactor, + int bonusMultiplier, + int additionalMultiplier) { var epForActivityLogEvent = new EpForActivityLogEvent(activityType) { @@ -486,36 +566,14 @@ private void LogEpForActivity(Account account, Character character, EpForActivit EventMultiplier = additionalMultiplier, }; - _epForActivityLogger.Log(epForActivityLogEvent); + 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"), - 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(); + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/Perpetuum/Accounting/IAccountManager.cs b/src/Perpetuum/Accounting/IAccountManager.cs index 95b678f40..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, int multiplierBonus); - //void ExtensionSubscriptionExtend(Account account, DateTime extendedValidUntil); 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/Items/RespecToken.cs b/src/Perpetuum/Items/RespecToken.cs new file mode 100644 index 000000000..51f25414f --- /dev/null +++ b/src/Perpetuum/Items/RespecToken.cs @@ -0,0 +1,34 @@ +using Perpetuum.Accounting; +using Perpetuum.Accounting.Characters; + +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) + { + var epData = accountManager.GetEPData(account, character); + + accountManager.FreeLockedEp(account, (int)epData[k.lockedEp]); + + var defaultExtensions = character.GetDefaultExtensions(); + + character.ResetAllExtensions(); + character.SetExtensions(defaultExtensions); + } + } +} diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 3bea35086..fc7f71edb 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -220,6 +220,7 @@ + From 47707a372488ee84ea6c394d61faa43dd9d3004f Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Thu, 24 Aug 2023 00:52:37 +0300 Subject: [PATCH 358/457] - Removing files with wrong encoding # Conflicts: # src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs # src/Perpetuum/Services/TechTree/TechTreeService.cs # Conflicts: # src/Perpetuum/Converters/DelegateConverter.cs # src/Perpetuum/Services/TechTree/Points.cs # src/Perpetuum/Services/TechTree/TechTreeNode.cs # src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs # src/Perpetuum/Services/TechTree/TechTreeService.cs # src/Perpetuum/Threading/LockFree.cs # src/Perpetuum/Wallets/Wallet.cs --- src/Perpetuum/Converters/DelegateConverter.cs | Bin 848 -> 0 bytes src/Perpetuum/Perpetuum.csproj | 7 ------- src/Perpetuum/Services/TechTree/Points.cs | Bin 1474 -> 0 bytes src/Perpetuum/Services/TechTree/TechTreeNode.cs | Bin 9086 -> 0 bytes .../Services/TechTree/TechTreePointsHandler.cs | Bin 5518 -> 0 bytes .../Services/TechTree/TechTreeService.cs | Bin 2842 -> 0 bytes src/Perpetuum/Threading/LockFree.cs | Bin 1918 -> 0 bytes src/Perpetuum/Wallets/Wallet.cs | Bin 2886 -> 0 bytes 8 files changed, 7 deletions(-) delete mode 100644 src/Perpetuum/Converters/DelegateConverter.cs delete mode 100644 src/Perpetuum/Services/TechTree/Points.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs delete mode 100644 src/Perpetuum/Threading/LockFree.cs delete mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs deleted file mode 100644 index 131164f216c72edccb5c345143022be8e314d734..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 848 zcmbV~&1wQM6ot>a(09mk3dIM|wj15JQqX;|(?Nt$hw%qQe0A%WOfO@`VG1QAxi`5# z=bYr{deBZQZM2lrOeeg(GOabxv!24-Xn~(`Q(&c7PE*zrKO_3USgWU@w$wR>8(*U& z!`C>6)sf{#LG+h$dZu_O)`GYl_K2LEx#Yy>Kh}HGTw{8?5=TAfRcd9eqwTRaf9sKSBB?ohotr3LqE|5l z?TXlK8yly06nU&)v9wIvFQM!G8`w?E-LN;cPJVxY LT}%vp#p|LQH_(0T diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 38a9c86aa..faa41e59d 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,7 +163,6 @@ - @@ -582,7 +581,6 @@ - @@ -622,7 +620,6 @@ - @@ -695,13 +692,9 @@ - - - - diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs deleted file mode 100644 index 7ee370cbecf40d6ad9cb06a21fa5fd7d197e6b94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1474 zcma)5+fKqj5IxT({=b^w-rhyB)iw%O#}j&g{&Y zGq>NL4c1s7$14mb*iy2{@PP#PSYgRuh8cAWN;zK>JW`YME2*2|8ke|0h%$C>1^+Ge z8;3Q*Hwq$@4t7uo4N}(a%Zk`d+rv$W^Ujv-E;S;`^4p*_T5kJ z$_?>1aL1T&fnOuc?YnK<8R0X*z2jCXTka6E4~=oVUd9;zXIRBGR@8UPjehBF+!*Vc zc#BTl8(2qq(}gzLH-hdEPr_txD1uONee zW32iJ9-<#Ae1_04#b5Pc?yp$o31n36dwh1>OZPA4if1Tc;C{%?*-Uh%D+nuLG~RlN z)WQ`XLL*}~gjXs&9l=YLJMil`^}jE0N8&12P-%J{Lr$qwdY4+MWeeBG(om1WHnd#9 zcVlnM+5NB8Ex1{rMy=QDGG4fikf-zYnxaMHu@5_5xJ_?Oeg!ekbf)FyAd6@#je#Y7 zw*s zV2x&Vkjj$7P=y*TOg{rMfA(B>d0#_cCPUdzFg{9feTx4cz_r(mlMJ6!oSv}aRQ_~fy)ZbVcN3uhBgFDvD zUMCi}{bmqh#2Coy+w~lm?`^y3 z4%m{sWyG~BSjR3)``m8QnW>bx@4C%rCUX7(kl9Uw^v%*qQC8epad(x&GNZ!r?C_=E z8~fc@Zu~mXFPW#2&0X&@)uS|Tudu^n_sAS2TG$bWjFtS{i|#x|UVa8&Sa|0?a&A76 zXtiNh=C_R!Spkbgs>}@cWX%5JzT)lo#c#pS+KQfk1&`C$O}k|JpPjwLh`qXWJr>hS z&p~|W>IV6C1dKAy(@y-Vy$HJ__JmrgN^Bu=q|<3)G}REZn1vP5-xlk3eeV|C8Ij*h zxYO23AAXhbo#tp+UC2c#t>Rkq#S+z!yI!E;(~f6RoXJb9#XRGRyVQ1Ti6x#(@Qfvn zhpve%)f7uz`0Jbr`0+;|m3_XgyUZi;Tsxw~aXd<`>x139od2i=c?Q-I zX}2<2kxzJXID?;gqA4d})gtCRCD7A>3C;}aR%y9=WPjkZp; zx>>i5O5iUAK5YR#B+foANN z;xo-V?!4P-cC%j1o)k$RGv}VlrpL%PjgmxN&k6O79{b@6of^@oJYtQp@u89Uc=1Xx zkn(-L;?QRChwcW{61{fMI@tkf*YgW1!4&+=cfKcJ&Tc!`YhTY;A&=3&4j`$i<1LGZ z?s3j*Di?Ij5}b9&7U{gy{i6jMnwBV|Ms+6 z4_e=TtZEl)Y?%#dHr3hRl{uhnCB;Wa;LK?fF z`p!XjReU>hfe4>toXu?WMx>>FN?9k?A8WC__xS!Hm*<(ULSOoS`{!A$506(+>LJYm I@zatM8gY6&$D z=ajdJIU{^$eNAcKk}DZNdx1H*bYvfB(LU2gyn2v!rq2dyPYzU5vi2~hkPF3#655bP zBu{WHaF;9kH~O1F20sJ%{T+xC*9_lNXc*#?&CK0}l|*NRw{J132WuIrB9a$N_gWaa z3(TUInObv*`Hb%=JkIdTd-CNQ?k;gp$qqweV3O<_+db`PxvkAaWa;;Gv|G^l0^bqd zbm4I`F{UkBc-Ik*y}|cZk$6~ZJuV)VR)n}Al1AR%hNxz;3TwSQhq8^d>%87vKoU{N z*ogWTgYcejQ+KW7+eFA$Sd9I7!QKMP1@ zuCO8$>J_otM2@uOBWxinufc|v)`&jx@@$d|( zcFeC^ zn4DB*nanQOOvK_GdZrbp2l?#o)reK+sMo`aL+?UOZly4i&tTapwB>hoV+}Svzr)X@ zlwIqPm|tfj%5~;by4sp02d zs7g-A-}~CMs(h*Py-I^w%h+@ESfC%(XQSnV%SQGrvam|eeEeUuBZU~(^%-_A_N}-} zF%s)j&%*BG>&?1$xkY_6A<75wu9zwN7&ACpCmv#hJCJ-ngQ_M@8+;tRnW& z!P);WWgE3~OU|L6)r<9t-DeAL`M%KIQ3tW%%6<=WcA_^9WQy^yW)`naoG75R0~QldY{t1b3$R#4X+xX)>V)uDv-q>PyPEiiFWE-bSC z`S;aN9oKzV@N2v!VBk7^E8+XY`uYFn+IGOs`{r7Cmyh?ges8sbs@K6;1-aV=dk=Nj zo=1IuA3WZ~TYq-K{S0?2+sWrE$TsdY82e+bHGN3^i43F!_Nq=o8f#KGFOy%G!96@X zL?d9(ury1mdt6HEmx-3I6Foc;8r9(8c4_6P&{ntG;O=ZyJs#+(Ng0l<()C+aMB}rM zZB%rwgn6RF^Ch0XaGe^iw~76{9;WJaHNN2r=DvH|*?JP!+sE16vKrS_K40S*^SHwD zBq!W^Ip(lp+gT)cG{zC0O)?Z*B~ZU1L+0V|odRKnk87N*dUx_1HiH9JjNXu{xVF1g4mUt0 z=T74s$yk}5q81^H0T{W0MLnXg%5(FHuTMFa0==Alovo_>D3EPiBo zq=Si1QB|*VPOE;U-8I2;uX_$1ouP^|c;~EAS90u7SJv=X?vQexTi7uMPIu8QKL6LH zoenaLb*6Ku8~O68N32X^tXiLz&hq4$+`uwHEIdKJyf@1#f)}E)@E_ljBB^$yjlPjR7y9qpdOdRfeObNZ-CHhnR& zPN64cq)-^1Y0lUV*bm8fSNg|fS=Fyri6#1t*t!1Gp%Pm)hB_U^Kc@4Nqqwkl(_Yk+mp z%IwbW{PWLg^ZQq&oeHgWqeN3ZvfC@yO7HbnZ`5}>(Y0<>ax>NZ**;pB)f(-Jvz@lA zGFFMM_*=5i!LRtO)P-TzKZEB48~bg!JJS*$89QT_^2J?-K1H|VLMW2xTQ`S ze4Sx8Z$G`mUkQ?B*}Q*eU%(*I2fj7D?vc~h=?GtSr5vj*jr$I~1t?aN_POte8TzdS zydFTZe2xxT7_-~*8QXU~dF;-Myuje4R(P_KI!r zSuk<2XO_=77?X`y8NYw5bX=3m5KYW#1)}$ENvv(Z$8&N7U56jKm_gPI`GV)W3bv zx!$dxorE_%1HPQ`py;{k<5Oz20sXB1(zE OC(85hvYF|)d(t0hmhx8s diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs deleted file mode 100644 index fc0bae2ee34046344e131e1eb38cfd8ba552dade..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2886 zcmb_eO>fgc5S=p;|6xHK5~<1$0NMiuAxt!VynaP#3a*Zz?-ZxmSq|wp zrdfPV&)=(kt$-xRWPl|zM0y9mjCKvLL0%}@8mr8$@6l}A=rgdiJ(JJyafv-{@G!I7 z;{?wXe$O?}zQk}|rqK0sv)5RqoG`RrobNSTtO{m5fFli7m;oPA&d@N(8$W(AV;;6s zsMBgH{pW1;ET3k{9dgmT?6Rnx);B5ER*R@Dvg(Ny@KJr_NSXVXKM%86wb8hoR+k@P z)39+IdXrA=N9?z{pAI{SXif65ahSQ$ZXTy`wud=VqG45&(=F<*%*knA(MM^|=8VHr zvgM}eLF|`L$Ce-;%+}p&lWjiWIp9#Yp^t$Wu*?;J-KEATQ9q+xwInHls_upAI z&d*_gV{%=h${Cv8h0ahJrSuXR=G`#fm{Mu$t$D1-P>-egeXRjUcw6L+i5}t^$636C nUg3nCgRi#of47sgeuBJ`mqlIh-uE!r3-*xr`&iD}INQq~1FU=R From 36c1fbaca5f3d9952b6341a910a5d06d1994662d Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Thu, 24 Aug 2023 01:00:58 +0300 Subject: [PATCH 359/457] - Re-adding files with correct encoding --- src/Perpetuum/Converters/DelegateConverter.cs | 19 +++ src/Perpetuum/Perpetuum.csproj | 7 + src/Perpetuum/Services/TechTree/Points.cs | 28 ++++ .../Services/TechTree/TechTreeNode.cs | 139 ++++++++++++++++++ .../TechTree/TechTreePointsHandler.cs | 77 ++++++++++ .../Services/TechTree/TechTreeService.cs | 48 ++++++ src/Perpetuum/Threading/LockFree.cs | 36 +++++ src/Perpetuum/Wallets/Wallet.cs | 60 ++++++++ 8 files changed, 414 insertions(+) create mode 100644 src/Perpetuum/Converters/DelegateConverter.cs create mode 100644 src/Perpetuum/Services/TechTree/Points.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs create mode 100644 src/Perpetuum/Threading/LockFree.cs create mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs new file mode 100644 index 000000000..c16333d90 --- /dev/null +++ b/src/Perpetuum/Converters/DelegateConverter.cs @@ -0,0 +1,19 @@ +using System; + +namespace Perpetuum.Converters +{ + public class DelegateConverter : IConverter + { + private readonly Converter _converter; + + public DelegateConverter(Converter converter) + { + _converter = converter; + } + + public TOut Convert(TIn item) + { + return _converter(item); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index faa41e59d..3bea35086 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,6 +163,7 @@ + @@ -565,6 +566,10 @@ + + + + @@ -581,6 +586,7 @@ + @@ -620,6 +626,7 @@ + diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs new file mode 100644 index 000000000..b1acd4c83 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/Points.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public struct Points + { + public static readonly Points Empty = new Points(TechTreePointType.undefined, 0); + + public readonly TechTreePointType type; + public readonly int amount; + + public Points(TechTreePointType type, int amount) + { + this.type = type; + this.amount = amount; + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"pointType",(int)type}, + {"amount",amount} + }; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeNode.cs b/src/Perpetuum/Services/TechTree/TechTreeNode.cs new file mode 100644 index 000000000..9b96151ca --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeNode.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeNode : IEquatable + { + private readonly int _definition; + private readonly int _parentDefinition; + private readonly TechTreeGroup _group; + private readonly int _x; + private readonly int _y; + + public int X => _x; + public int Definition => _definition; + public int ParentDefinition => _parentDefinition; + public TechTreeGroup Group => _group; + + public IEnumerable Prices { get; set; } + + private TechTreeNode(int definition, int parentDefinition, TechTreeGroup group, int x, int y) + { + _definition = definition; + _parentDefinition = parentDefinition; + _group = group; + _x = x; + _y = y; + } + + public static TechTreeNode CreateFromDataRecord(IDataRecord record) + { + var definition = record.GetValue("childdefinition"); + var parentDefinition = record.GetValue("parentdefinition"); + var group = record.GetValue("groupId"); + var x = record.GetValue("x"); + var y = record.GetValue("y"); + return new TechTreeNode(definition, parentDefinition, group, x, y); + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"definition", Definition}, + {"parentdefinition", ParentDefinition}, + {"groupId", (int)Group}, + {"x", X}, + {"y", _y}, + {"prices",Prices.ToDictionary("p",p => p.ToDictionary())} + }; + } + + public override string ToString() + { + return $"Definition: {Definition}, ParentDefinition: {ParentDefinition}, GroupId: {Group}, X: {X}, Y: {_y}"; + } + + public List Traverse(IDictionary nodes) + { + var r = new List(); + + var parentDefinition = ParentDefinition; + + while (parentDefinition > 0) + { + TechTreeNode parentNode; + if (!nodes.TryGetValue(parentDefinition, out parentNode)) + break; + + r.Add(parentNode); + parentDefinition = parentNode.ParentDefinition; + } + + return r; + } + + public Extension GetEnablerExtension(IDictionary groups) + { + var enablerExtensionId = groups[Group].enablerExtensionId; + return new Extension(enablerExtensionId, X + 1); + } + + public bool Equals(TechTreeNode other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return _y == other._y && Definition == other.Definition && ParentDefinition == other.ParentDefinition && Group == other.Group && X == other.X; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((TechTreeNode)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = _definition; + hashCode = (hashCode * 397) ^ _parentDefinition; + hashCode = (hashCode * 397) ^ (int)_group; + hashCode = (hashCode * 397) ^ _x; + hashCode = (hashCode * 397) ^ _y; + return hashCode; + } + } + + public static bool operator ==(TechTreeNode left, TechTreeNode right) + { + return Equals(left, right); + } + + public static bool operator !=(TechTreeNode left, TechTreeNode right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs new file mode 100644 index 000000000..5c2a928b6 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Accounting.Characters; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreePointsHandler + { + private readonly long _owner; + + public TechTreePointsHandler(Character character) : this(character.Eid) { } + + public TechTreePointsHandler(long owner) + { + _owner = owner; + } + + public void UpdatePoints(TechTreePointType pointType, Func pointsUpdater) + { + var record = Db.Query() + .CommandText("select top 1 id,amount from techtreepoints (UPDLOCK) where owner = @owner and pointType = @pointType") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .ExecuteSingleRow(); + + var id = 0; + var currentPoints = 0; + + if (record != null) + { + id = record.GetValue(0); + currentPoints = record.GetValue("amount"); + } + + var updatedPoints = pointsUpdater(currentPoints); + + if (updatedPoints < 0) + updatedPoints = 0; + + if (updatedPoints == currentPoints) + return; + + if (id > 0) + { + Db.Query().CommandText("update techtreepoints set amount = @amount where id = @id") + .SetParameter("@id", id) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); + } + else + { + Db.Query().CommandText("insert into techtreepoints (owner,pointType,amount) values (@owner,@pointType,@amount)") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + } + } + + public void AddAvailablePointsToDictionary(IDictionary dictionary, string key = "points") + { + dictionary[key] = GetAvailablePoints().ToDictionary("p", p => p.ToDictionary()); + } + + private IEnumerable GetAvailablePoints() + { + return Db.Query() + .CommandText("select pointType,amount from techtreepoints (UPDLOCK) where owner = @owner") + .SetParameter("@owner", _owner) + .Execute() + .Select(r => new Points(r.GetValue(0), r.GetValue(1))); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs new file mode 100644 index 000000000..c7ee4e801 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Data; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeService : ITechTreeService + { + private readonly ITechTreeInfoService _infoService; + + public TechTreeService(ITechTreeInfoService infoService) + { + _infoService = infoService; + } + + public void NodeUnlocked(long owner, TechTreeNode node) + { + + } + + public IEnumerable GetUnlockedNodes(long owner) + { + var nodes = _infoService.GetNodes(); + return Db.Query() + .CommandText("select definition from techtreeunlockednodes where owner = @owner") + .SetParameter("@owner", owner) + .Execute() + .Select(r => nodes[r.GetValue(0)]).ToArray(); + } + + public void AddInfoToDictionary(long owner, IDictionary dictionary) + { + dictionary["techTree"] = GetInfo(owner); + } + + private IDictionary GetInfo(long owner) + { + var info = new Dictionary + { + {"unlockedNodes", GetUnlockedNodes(owner).Select(n => n.Definition).ToArray()}, + }; + + var points = new TechTreePointsHandler(owner); + points.AddAvailablePointsToDictionary(info); + return info; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs new file mode 100644 index 000000000..622c3210f --- /dev/null +++ b/src/Perpetuum/Threading/LockFree.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; + +namespace Perpetuum.Threading +{ + public static class LockFree + { + public static void Update(ref uint destination, Func updater) + { + var spinWait = new SpinWait(); + + unsafe + { + fixed (uint* p = &destination) + { + while (true) + { + var original = *p; + var value = updater(original); + + if (Interlocked.CompareExchange(ref *(int*)p, (int)value, (int)original) == original) + return; + + spinWait.SpinOnce(); + } + } + } + } + + + public static void Swap(ref T a, ref T b) where T : class + { + Interlocked.Exchange(ref a, Interlocked.Exchange(ref b, a)); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs new file mode 100644 index 000000000..d3229dc4e --- /dev/null +++ b/src/Perpetuum/Wallets/Wallet.cs @@ -0,0 +1,60 @@ +using System.Transactions; +using Perpetuum.Data; + +namespace Perpetuum.Wallets +{ + public abstract class Wallet : IWallet + { + private TCredit _startBalance; + private bool _first = true; + + protected Wallet() + { + if (Transaction.Current != null) + Transaction.Current.OnCommited(() => OnCommited(_startBalance)); + } + + protected virtual void OnCommited(TCredit startBalance) + { + + } + + public TCredit Balance + { + get + { + var balance = GetBalance(); + + if (_first) + { + _first = false; + _startBalance = balance; + } + + return balance; + } + set + { + var current = GetBalance(); + + if (Equals(current, value)) + return; + + OnBalanceUpdating(current, value); + + SetBalance(value); + } + } + + protected abstract void SetBalance(TCredit value); + protected abstract TCredit GetBalance(); + + protected abstract void OnBalanceUpdating(TCredit currentCredit, TCredit desiredCredit); + + + public override string ToString() + { + return $"Balance: {GetBalance()}"; + } + } +} \ No newline at end of file From c21b1ab3fcee45c32da9ed0ae85f0d859ac27bbe Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Mon, 4 Sep 2023 17:33:17 +0300 Subject: [PATCH 360/457] Spark Teleport Device - Renaming Spark Teleport Token into Spark Teleport Device --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 2 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 2 +- .../Items/{SparkTeleportToken.cs => SparkTeleportDevice.cs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/Perpetuum/Items/{SparkTeleportToken.cs => SparkTeleportDevice.cs} (100%) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 229f1fb6a..83fbcad1d 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1248,7 +1248,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_spark_teleport_tokens); + ByCategoryFlags(CategoryFlags.cf_spark_teleport_devices); // OPP new Blinder module ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 4ce39be48..587a115ef 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -665,6 +665,6 @@ public enum CategoryFlags : long cf_zone_item_shop = 0x0000000000000796, cf_zone_objects = 0x0000000000000092, def_pbs_dogtags = 0x0000000000000691, - cf_spark_teleport_tokens = 0x000000000005049B, + cf_spark_teleport_devices = 0x000000000005049B, } } diff --git a/src/Perpetuum/Items/SparkTeleportToken.cs b/src/Perpetuum/Items/SparkTeleportDevice.cs similarity index 100% rename from src/Perpetuum/Items/SparkTeleportToken.cs rename to src/Perpetuum/Items/SparkTeleportDevice.cs From a0bf6671deb826c2d38ca223dbb29d0fe625d742 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Mon, 4 Sep 2023 20:51:37 +0300 Subject: [PATCH 361/457] Spark Teleport Device - Renaming Spark Teleport Token into Spark Teleport Device --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 4 ++-- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1 + src/Perpetuum.RequestHandlers/UseLotteryItem.cs | 4 ++-- src/Perpetuum/Items/SparkTeleportDevice.cs | 2 +- src/Perpetuum/Perpetuum.csproj | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 83fbcad1d..1dbee450b 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -920,7 +920,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); - RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -1248,7 +1248,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_spark_teleport_devices); + ByCategoryFlags(CategoryFlags.cf_spark_teleport_devices); // OPP new Blinder module ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 587a115ef..417076cac 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -665,6 +665,7 @@ public enum CategoryFlags : long cf_zone_item_shop = 0x0000000000000796, cf_zone_objects = 0x0000000000000092, def_pbs_dogtags = 0x0000000000000691, + cf_respec_tokens = 0x000000000004049B, cf_spark_teleport_devices = 0x000000000005049B, } } diff --git a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index 85917c7f3..15f2c860d 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -60,7 +60,7 @@ public void HandleRequest(IRequest request) { HandleCalibrationTemplateItem(request, itemEid); } - else if (item is SparkTeleportToken) + else if (item is SparkTeleportDevice) { HandleSparkTeleportToken(request, itemEid); } @@ -257,7 +257,7 @@ private void HandleSparkTeleportToken(IRequest request, long itemEid) var container = Container.GetWithItems(containerEid, character); - var containerItem = (SparkTeleportToken)container + var containerItem = (SparkTeleportDevice)container .GetItemOrThrow(itemEid, true) .Unstack(1); diff --git a/src/Perpetuum/Items/SparkTeleportDevice.cs b/src/Perpetuum/Items/SparkTeleportDevice.cs index e123f556b..cdedfe94d 100644 --- a/src/Perpetuum/Items/SparkTeleportDevice.cs +++ b/src/Perpetuum/Items/SparkTeleportDevice.cs @@ -12,7 +12,7 @@ namespace Perpetuum.Items { - public class SparkTeleportToken : Item + public class SparkTeleportDevice : Item { public int BaseId { diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 589622ad7..bf84bf6e8 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -225,7 +225,7 @@ - + From 105abe22aac4977b42c0d3563abead61fc476dad Mon Sep 17 00:00:00 2001 From: Ghroom Date: Mon, 27 Sep 2021 11:25:08 +0200 Subject: [PATCH 362/457] Logging - Chat Commands (#315) --- .../Services/Channels/ChannelManager.cs | 12 +++++- .../ChatCommands/GameAdminCommands.cs | 40 ++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index f32ff95ca..fb4b17617 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -266,9 +266,17 @@ public void Talk(string channelName, Character sender, string message, IRequest channel.Logger.LogMessage(sender, message); m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); - channel.SendMessageToAll(_sessionManager, sender, message); - _adminCommand.TryParseAdminCommand(sender, message, request, channel, this); + //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) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index fedf73b90..a7754be4b 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -1,6 +1,7 @@ using Perpetuum.Accounting.Characters; using Perpetuum.Host.Requests; using Perpetuum.Services.Sessions; +using Perpetuum.Data; using System; using System.Collections.Generic; using System.Linq; @@ -30,21 +31,47 @@ public AdminCommandRouter(GlobalConfiguration configuration, ISessionManager ses public void TryParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) { - if (IsAdminCommand(sender, text)) + var b_isAdmin = IsAdmin(sender); + if (b_isAdmin) { + WriteLogToDb(sender, text, b_isAdmin); //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, b_isAdmin); + } + } + + public bool IsAdminCommand(string text) + { + return text.StartsWith("#"); + } + private bool IsAdmin(Character sender) + { + return sender.AccessLevel == AccessLevel.admin; } - private bool IsAdminCommand(Character sender, string message) + private void WriteLogToDb(Character sender, string text, bool badmin) { - return message.StartsWith("#") && sender.AccessLevel == AccessLevel.admin; + var str_trunc = text; + var adminright = sender.AccessLevel; + + if (text.Length > 255) + { + str_trunc = text.Substring(1, 255); //text truncated to avoid big text spam insertion in db + } + + Db.Query().CommandText("insert 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) { - if (!IsAdminCommand(sender, text)) - return; string[] command = text.Split(new char[] { ',' }); @@ -60,6 +87,9 @@ private void ParseAdminCommand(Character sender, string text, IRequest request, // 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; } From b235919f474f7da4a41a5db19496842cd8b824bb Mon Sep 17 00:00:00 2001 From: Ghroom Date: Mon, 27 Sep 2021 12:16:05 +0200 Subject: [PATCH 363/457] added dipslay of the command if it's an admin --- .../Services/Channels/ChatCommands/GameAdminCommands.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index a7754be4b..82ce85c8f 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -34,6 +34,7 @@ public void TryParseAdminCommand(Character sender, string text, IRequest request var b_isAdmin = IsAdmin(sender); if (b_isAdmin) { + channel.SendMessageToAll(_sessionManager, sender, text); //in the future, it will be displayed only in the secure channel WriteLogToDb(sender, text, b_isAdmin); //enhancement todo: put the log in the next part to be able to log all the admin details ParseAdminCommand(sender, text, request, channel, channelManager); } From 6fbb1927118db0f8b49f9e6ef729852766cee97f Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 19:34:37 +0300 Subject: [PATCH 364/457] CommandLog - Style-guide fixes. also removed unused variables --- .../Channels/ChatCommands/GameAdminCommands.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index 82ce85c8f..b4b1f1385 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -31,16 +31,16 @@ public AdminCommandRouter(GlobalConfiguration configuration, ISessionManager ses public void TryParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) { - var b_isAdmin = IsAdmin(sender); - if (b_isAdmin) + 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, b_isAdmin); //enhancement todo: put the log in the next part to be able to log all the admin details + 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, b_isAdmin); + WriteLogToDb(sender, text); } } @@ -53,10 +53,9 @@ private bool IsAdmin(Character sender) return sender.AccessLevel == AccessLevel.admin; } - private void WriteLogToDb(Character sender, string text, bool badmin) + private void WriteLogToDb(Character sender, string text) { var str_trunc = text; - var adminright = sender.AccessLevel; if (text.Length > 255) { From 0bde4e9832dd918e42fa9241b95573e2bde39133 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 19:37:36 +0300 Subject: [PATCH 365/457] CommandLog - Removing files with wrong encoding --- src/Perpetuum/Converters/DelegateConverter.cs | Bin 848 -> 0 bytes src/Perpetuum/Perpetuum.csproj | 7 ------- src/Perpetuum/Services/TechTree/Points.cs | Bin 1474 -> 0 bytes src/Perpetuum/Services/TechTree/TechTreeNode.cs | Bin 9086 -> 0 bytes .../Services/TechTree/TechTreePointsHandler.cs | Bin 5518 -> 0 bytes .../Services/TechTree/TechTreeService.cs | Bin 2842 -> 0 bytes src/Perpetuum/Threading/LockFree.cs | Bin 1918 -> 0 bytes src/Perpetuum/Wallets/Wallet.cs | Bin 2886 -> 0 bytes 8 files changed, 7 deletions(-) delete mode 100644 src/Perpetuum/Converters/DelegateConverter.cs delete mode 100644 src/Perpetuum/Services/TechTree/Points.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs delete mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs delete mode 100644 src/Perpetuum/Threading/LockFree.cs delete mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs deleted file mode 100644 index 131164f216c72edccb5c345143022be8e314d734..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 848 zcmbV~&1wQM6ot>a(09mk3dIM|wj15JQqX;|(?Nt$hw%qQe0A%WOfO@`VG1QAxi`5# z=bYr{deBZQZM2lrOeeg(GOabxv!24-Xn~(`Q(&c7PE*zrKO_3USgWU@w$wR>8(*U& z!`C>6)sf{#LG+h$dZu_O)`GYl_K2LEx#Yy>Kh}HGTw{8?5=TAfRcd9eqwTRaf9sKSBB?ohotr3LqE|5l z?TXlK8yly06nU&)v9wIvFQM!G8`w?E-LN;cPJVxY LT}%vp#p|LQH_(0T diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 38a9c86aa..faa41e59d 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,7 +163,6 @@ - @@ -582,7 +581,6 @@ - @@ -622,7 +620,6 @@ - @@ -695,13 +692,9 @@ - - - - diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs deleted file mode 100644 index 7ee370cbecf40d6ad9cb06a21fa5fd7d197e6b94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1474 zcma)5+fKqj5IxT({=b^w-rhyB)iw%O#}j&g{&Y zGq>NL4c1s7$14mb*iy2{@PP#PSYgRuh8cAWN;zK>JW`YME2*2|8ke|0h%$C>1^+Ge z8;3Q*Hwq$@4t7uo4N}(a%Zk`d+rv$W^Ujv-E;S;`^4p*_T5kJ z$_?>1aL1T&fnOuc?YnK<8R0X*z2jCXTka6E4~=oVUd9;zXIRBGR@8UPjehBF+!*Vc zc#BTl8(2qq(}gzLH-hdEPr_txD1uONee zW32iJ9-<#Ae1_04#b5Pc?yp$o31n36dwh1>OZPA4if1Tc;C{%?*-Uh%D+nuLG~RlN z)WQ`XLL*}~gjXs&9l=YLJMil`^}jE0N8&12P-%J{Lr$qwdY4+MWeeBG(om1WHnd#9 zcVlnM+5NB8Ex1{rMy=QDGG4fikf-zYnxaMHu@5_5xJ_?Oeg!ekbf)FyAd6@#je#Y7 zw*s zV2x&Vkjj$7P=y*TOg{rMfA(B>d0#_cCPUdzFg{9feTx4cz_r(mlMJ6!oSv}aRQ_~fy)ZbVcN3uhBgFDvD zUMCi}{bmqh#2Coy+w~lm?`^y3 z4%m{sWyG~BSjR3)``m8QnW>bx@4C%rCUX7(kl9Uw^v%*qQC8epad(x&GNZ!r?C_=E z8~fc@Zu~mXFPW#2&0X&@)uS|Tudu^n_sAS2TG$bWjFtS{i|#x|UVa8&Sa|0?a&A76 zXtiNh=C_R!Spkbgs>}@cWX%5JzT)lo#c#pS+KQfk1&`C$O}k|JpPjwLh`qXWJr>hS z&p~|W>IV6C1dKAy(@y-Vy$HJ__JmrgN^Bu=q|<3)G}REZn1vP5-xlk3eeV|C8Ij*h zxYO23AAXhbo#tp+UC2c#t>Rkq#S+z!yI!E;(~f6RoXJb9#XRGRyVQ1Ti6x#(@Qfvn zhpve%)f7uz`0Jbr`0+;|m3_XgyUZi;Tsxw~aXd<`>x139od2i=c?Q-I zX}2<2kxzJXID?;gqA4d})gtCRCD7A>3C;}aR%y9=WPjkZp; zx>>i5O5iUAK5YR#B+foANN z;xo-V?!4P-cC%j1o)k$RGv}VlrpL%PjgmxN&k6O79{b@6of^@oJYtQp@u89Uc=1Xx zkn(-L;?QRChwcW{61{fMI@tkf*YgW1!4&+=cfKcJ&Tc!`YhTY;A&=3&4j`$i<1LGZ z?s3j*Di?Ij5}b9&7U{gy{i6jMnwBV|Ms+6 z4_e=TtZEl)Y?%#dHr3hRl{uhnCB;Wa;LK?fF z`p!XjReU>hfe4>toXu?WMx>>FN?9k?A8WC__xS!Hm*<(ULSOoS`{!A$506(+>LJYm I@zatM8gY6&$D z=ajdJIU{^$eNAcKk}DZNdx1H*bYvfB(LU2gyn2v!rq2dyPYzU5vi2~hkPF3#655bP zBu{WHaF;9kH~O1F20sJ%{T+xC*9_lNXc*#?&CK0}l|*NRw{J132WuIrB9a$N_gWaa z3(TUInObv*`Hb%=JkIdTd-CNQ?k;gp$qqweV3O<_+db`PxvkAaWa;;Gv|G^l0^bqd zbm4I`F{UkBc-Ik*y}|cZk$6~ZJuV)VR)n}Al1AR%hNxz;3TwSQhq8^d>%87vKoU{N z*ogWTgYcejQ+KW7+eFA$Sd9I7!QKMP1@ zuCO8$>J_otM2@uOBWxinufc|v)`&jx@@$d|( zcFeC^ zn4DB*nanQOOvK_GdZrbp2l?#o)reK+sMo`aL+?UOZly4i&tTapwB>hoV+}Svzr)X@ zlwIqPm|tfj%5~;by4sp02d zs7g-A-}~CMs(h*Py-I^w%h+@ESfC%(XQSnV%SQGrvam|eeEeUuBZU~(^%-_A_N}-} zF%s)j&%*BG>&?1$xkY_6A<75wu9zwN7&ACpCmv#hJCJ-ngQ_M@8+;tRnW& z!P);WWgE3~OU|L6)r<9t-DeAL`M%KIQ3tW%%6<=WcA_^9WQy^yW)`naoG75R0~QldY{t1b3$R#4X+xX)>V)uDv-q>PyPEiiFWE-bSC z`S;aN9oKzV@N2v!VBk7^E8+XY`uYFn+IGOs`{r7Cmyh?ges8sbs@K6;1-aV=dk=Nj zo=1IuA3WZ~TYq-K{S0?2+sWrE$TsdY82e+bHGN3^i43F!_Nq=o8f#KGFOy%G!96@X zL?d9(ury1mdt6HEmx-3I6Foc;8r9(8c4_6P&{ntG;O=ZyJs#+(Ng0l<()C+aMB}rM zZB%rwgn6RF^Ch0XaGe^iw~76{9;WJaHNN2r=DvH|*?JP!+sE16vKrS_K40S*^SHwD zBq!W^Ip(lp+gT)cG{zC0O)?Z*B~ZU1L+0V|odRKnk87N*dUx_1HiH9JjNXu{xVF1g4mUt0 z=T74s$yk}5q81^H0T{W0MLnXg%5(FHuTMFa0==Alovo_>D3EPiBo zq=Si1QB|*VPOE;U-8I2;uX_$1ouP^|c;~EAS90u7SJv=X?vQexTi7uMPIu8QKL6LH zoenaLb*6Ku8~O68N32X^tXiLz&hq4$+`uwHEIdKJyf@1#f)}E)@E_ljBB^$yjlPjR7y9qpdOdRfeObNZ-CHhnR& zPN64cq)-^1Y0lUV*bm8fSNg|fS=Fyri6#1t*t!1Gp%Pm)hB_U^Kc@4Nqqwkl(_Yk+mp z%IwbW{PWLg^ZQq&oeHgWqeN3ZvfC@yO7HbnZ`5}>(Y0<>ax>NZ**;pB)f(-Jvz@lA zGFFMM_*=5i!LRtO)P-TzKZEB48~bg!JJS*$89QT_^2J?-K1H|VLMW2xTQ`S ze4Sx8Z$G`mUkQ?B*}Q*eU%(*I2fj7D?vc~h=?GtSr5vj*jr$I~1t?aN_POte8TzdS zydFTZe2xxT7_-~*8QXU~dF;-Myuje4R(P_KI!r zSuk<2XO_=77?X`y8NYw5bX=3m5KYW#1)}$ENvv(Z$8&N7U56jKm_gPI`GV)W3bv zx!$dxorE_%1HPQ`py;{k<5Oz20sXB1(zE OC(85hvYF|)d(t0hmhx8s diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs deleted file mode 100644 index fc0bae2ee34046344e131e1eb38cfd8ba552dade..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2886 zcmb_eO>fgc5S=p;|6xHK5~<1$0NMiuAxt!VynaP#3a*Zz?-ZxmSq|wp zrdfPV&)=(kt$-xRWPl|zM0y9mjCKvLL0%}@8mr8$@6l}A=rgdiJ(JJyafv-{@G!I7 z;{?wXe$O?}zQk}|rqK0sv)5RqoG`RrobNSTtO{m5fFli7m;oPA&d@N(8$W(AV;;6s zsMBgH{pW1;ET3k{9dgmT?6Rnx);B5ER*R@Dvg(Ny@KJr_NSXVXKM%86wb8hoR+k@P z)39+IdXrA=N9?z{pAI{SXif65ahSQ$ZXTy`wud=VqG45&(=F<*%*knA(MM^|=8VHr zvgM}eLF|`L$Ce-;%+}p&lWjiWIp9#Yp^t$Wu*?;J-KEATQ9q+xwInHls_upAI z&d*_gV{%=h${Cv8h0ahJrSuXR=G`#fm{Mu$t$D1-P>-egeXRjUcw6L+i5}t^$636C nUg3nCgRi#of47sgeuBJ`mqlIh-uE!r3-*xr`&iD}INQq~1FU=R From 215705deae5cf0f605449b20d30a9c82dedc6508 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 19:38:15 +0300 Subject: [PATCH 366/457] CommandLog - Re-adding files with correct encoding --- src/Perpetuum/Converters/DelegateConverter.cs | 19 +++ src/Perpetuum/Perpetuum.csproj | 7 + src/Perpetuum/Services/TechTree/Points.cs | 28 ++++ .../Services/TechTree/TechTreeNode.cs | 139 ++++++++++++++++++ .../TechTree/TechTreePointsHandler.cs | 77 ++++++++++ .../Services/TechTree/TechTreeService.cs | 48 ++++++ src/Perpetuum/Threading/LockFree.cs | 36 +++++ src/Perpetuum/Wallets/Wallet.cs | 60 ++++++++ 8 files changed, 414 insertions(+) create mode 100644 src/Perpetuum/Converters/DelegateConverter.cs create mode 100644 src/Perpetuum/Services/TechTree/Points.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeNode.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs create mode 100644 src/Perpetuum/Services/TechTree/TechTreeService.cs create mode 100644 src/Perpetuum/Threading/LockFree.cs create mode 100644 src/Perpetuum/Wallets/Wallet.cs diff --git a/src/Perpetuum/Converters/DelegateConverter.cs b/src/Perpetuum/Converters/DelegateConverter.cs new file mode 100644 index 000000000..c16333d90 --- /dev/null +++ b/src/Perpetuum/Converters/DelegateConverter.cs @@ -0,0 +1,19 @@ +using System; + +namespace Perpetuum.Converters +{ + public class DelegateConverter : IConverter + { + private readonly Converter _converter; + + public DelegateConverter(Converter converter) + { + _converter = converter; + } + + public TOut Convert(TIn item) + { + return _converter(item); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index faa41e59d..3bea35086 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -163,6 +163,7 @@ + @@ -565,6 +566,10 @@ + + + + @@ -581,6 +586,7 @@ + @@ -620,6 +626,7 @@ + diff --git a/src/Perpetuum/Services/TechTree/Points.cs b/src/Perpetuum/Services/TechTree/Points.cs new file mode 100644 index 000000000..b1acd4c83 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/Points.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public struct Points + { + public static readonly Points Empty = new Points(TechTreePointType.undefined, 0); + + public readonly TechTreePointType type; + public readonly int amount; + + public Points(TechTreePointType type, int amount) + { + this.type = type; + this.amount = amount; + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"pointType",(int)type}, + {"amount",amount} + }; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeNode.cs b/src/Perpetuum/Services/TechTree/TechTreeNode.cs new file mode 100644 index 000000000..9b96151ca --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeNode.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeNode : IEquatable + { + private readonly int _definition; + private readonly int _parentDefinition; + private readonly TechTreeGroup _group; + private readonly int _x; + private readonly int _y; + + public int X => _x; + public int Definition => _definition; + public int ParentDefinition => _parentDefinition; + public TechTreeGroup Group => _group; + + public IEnumerable Prices { get; set; } + + private TechTreeNode(int definition, int parentDefinition, TechTreeGroup group, int x, int y) + { + _definition = definition; + _parentDefinition = parentDefinition; + _group = group; + _x = x; + _y = y; + } + + public static TechTreeNode CreateFromDataRecord(IDataRecord record) + { + var definition = record.GetValue("childdefinition"); + var parentDefinition = record.GetValue("parentdefinition"); + var group = record.GetValue("groupId"); + var x = record.GetValue("x"); + var y = record.GetValue("y"); + return new TechTreeNode(definition, parentDefinition, group, x, y); + } + + public IDictionary ToDictionary() + { + return new Dictionary + { + {"definition", Definition}, + {"parentdefinition", ParentDefinition}, + {"groupId", (int)Group}, + {"x", X}, + {"y", _y}, + {"prices",Prices.ToDictionary("p",p => p.ToDictionary())} + }; + } + + public override string ToString() + { + return $"Definition: {Definition}, ParentDefinition: {ParentDefinition}, GroupId: {Group}, X: {X}, Y: {_y}"; + } + + public List Traverse(IDictionary nodes) + { + var r = new List(); + + var parentDefinition = ParentDefinition; + + while (parentDefinition > 0) + { + TechTreeNode parentNode; + if (!nodes.TryGetValue(parentDefinition, out parentNode)) + break; + + r.Add(parentNode); + parentDefinition = parentNode.ParentDefinition; + } + + return r; + } + + public Extension GetEnablerExtension(IDictionary groups) + { + var enablerExtensionId = groups[Group].enablerExtensionId; + return new Extension(enablerExtensionId, X + 1); + } + + public bool Equals(TechTreeNode other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return _y == other._y && Definition == other.Definition && ParentDefinition == other.ParentDefinition && Group == other.Group && X == other.X; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((TechTreeNode)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = _definition; + hashCode = (hashCode * 397) ^ _parentDefinition; + hashCode = (hashCode * 397) ^ (int)_group; + hashCode = (hashCode * 397) ^ _x; + hashCode = (hashCode * 397) ^ _y; + return hashCode; + } + } + + public static bool operator ==(TechTreeNode left, TechTreeNode right) + { + return Equals(left, right); + } + + public static bool operator !=(TechTreeNode left, TechTreeNode right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs new file mode 100644 index 000000000..5c2a928b6 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreePointsHandler.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Accounting.Characters; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreePointsHandler + { + private readonly long _owner; + + public TechTreePointsHandler(Character character) : this(character.Eid) { } + + public TechTreePointsHandler(long owner) + { + _owner = owner; + } + + public void UpdatePoints(TechTreePointType pointType, Func pointsUpdater) + { + var record = Db.Query() + .CommandText("select top 1 id,amount from techtreepoints (UPDLOCK) where owner = @owner and pointType = @pointType") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .ExecuteSingleRow(); + + var id = 0; + var currentPoints = 0; + + if (record != null) + { + id = record.GetValue(0); + currentPoints = record.GetValue("amount"); + } + + var updatedPoints = pointsUpdater(currentPoints); + + if (updatedPoints < 0) + updatedPoints = 0; + + if (updatedPoints == currentPoints) + return; + + if (id > 0) + { + Db.Query().CommandText("update techtreepoints set amount = @amount where id = @id") + .SetParameter("@id", id) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLUpdateError); + } + else + { + Db.Query().CommandText("insert into techtreepoints (owner,pointType,amount) values (@owner,@pointType,@amount)") + .SetParameter("@owner", _owner) + .SetParameter("@pointType", (int)pointType) + .SetParameter("@amount", updatedPoints) + .ExecuteNonQuery().ThrowIfEqual(0, ErrorCodes.SQLInsertError); + } + } + + public void AddAvailablePointsToDictionary(IDictionary dictionary, string key = "points") + { + dictionary[key] = GetAvailablePoints().ToDictionary("p", p => p.ToDictionary()); + } + + private IEnumerable GetAvailablePoints() + { + return Db.Query() + .CommandText("select pointType,amount from techtreepoints (UPDLOCK) where owner = @owner") + .SetParameter("@owner", _owner) + .Execute() + .Select(r => new Points(r.GetValue(0), r.GetValue(1))); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Services/TechTree/TechTreeService.cs b/src/Perpetuum/Services/TechTree/TechTreeService.cs new file mode 100644 index 000000000..c7ee4e801 --- /dev/null +++ b/src/Perpetuum/Services/TechTree/TechTreeService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Perpetuum.Data; + +namespace Perpetuum.Services.TechTree +{ + public class TechTreeService : ITechTreeService + { + private readonly ITechTreeInfoService _infoService; + + public TechTreeService(ITechTreeInfoService infoService) + { + _infoService = infoService; + } + + public void NodeUnlocked(long owner, TechTreeNode node) + { + + } + + public IEnumerable GetUnlockedNodes(long owner) + { + var nodes = _infoService.GetNodes(); + return Db.Query() + .CommandText("select definition from techtreeunlockednodes where owner = @owner") + .SetParameter("@owner", owner) + .Execute() + .Select(r => nodes[r.GetValue(0)]).ToArray(); + } + + public void AddInfoToDictionary(long owner, IDictionary dictionary) + { + dictionary["techTree"] = GetInfo(owner); + } + + private IDictionary GetInfo(long owner) + { + var info = new Dictionary + { + {"unlockedNodes", GetUnlockedNodes(owner).Select(n => n.Definition).ToArray()}, + }; + + var points = new TechTreePointsHandler(owner); + points.AddAvailablePointsToDictionary(info); + return info; + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Threading/LockFree.cs b/src/Perpetuum/Threading/LockFree.cs new file mode 100644 index 000000000..622c3210f --- /dev/null +++ b/src/Perpetuum/Threading/LockFree.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; + +namespace Perpetuum.Threading +{ + public static class LockFree + { + public static void Update(ref uint destination, Func updater) + { + var spinWait = new SpinWait(); + + unsafe + { + fixed (uint* p = &destination) + { + while (true) + { + var original = *p; + var value = updater(original); + + if (Interlocked.CompareExchange(ref *(int*)p, (int)value, (int)original) == original) + return; + + spinWait.SpinOnce(); + } + } + } + } + + + public static void Swap(ref T a, ref T b) where T : class + { + Interlocked.Exchange(ref a, Interlocked.Exchange(ref b, a)); + } + } +} \ No newline at end of file diff --git a/src/Perpetuum/Wallets/Wallet.cs b/src/Perpetuum/Wallets/Wallet.cs new file mode 100644 index 000000000..d3229dc4e --- /dev/null +++ b/src/Perpetuum/Wallets/Wallet.cs @@ -0,0 +1,60 @@ +using System.Transactions; +using Perpetuum.Data; + +namespace Perpetuum.Wallets +{ + public abstract class Wallet : IWallet + { + private TCredit _startBalance; + private bool _first = true; + + protected Wallet() + { + if (Transaction.Current != null) + Transaction.Current.OnCommited(() => OnCommited(_startBalance)); + } + + protected virtual void OnCommited(TCredit startBalance) + { + + } + + public TCredit Balance + { + get + { + var balance = GetBalance(); + + if (_first) + { + _first = false; + _startBalance = balance; + } + + return balance; + } + set + { + var current = GetBalance(); + + if (Equals(current, value)) + return; + + OnBalanceUpdating(current, value); + + SetBalance(value); + } + } + + protected abstract void SetBalance(TCredit value); + protected abstract TCredit GetBalance(); + + protected abstract void OnBalanceUpdating(TCredit currentCredit, TCredit desiredCredit); + + + public override string ToString() + { + return $"Balance: {GetBalance()}"; + } + } +} \ No newline at end of file From 8e9896236c3944b16156081c96dc1a5438b8e7bf Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 20:21:16 +0300 Subject: [PATCH 367/457] CommandLog - minor script change --- .../Services/Channels/ChatCommands/GameAdminCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index b4b1f1385..f3cb8bf53 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -62,7 +62,7 @@ private void WriteLogToDb(Character sender, string text) str_trunc = text.Substring(1, 255); //text truncated to avoid big text spam insertion in db } - Db.Query().CommandText("insert adminCommandLog (characterid, acclevel, message) values (@characterid, @acclevel, @text)") + 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) From d4a3f8aa426aa5089f950191886a12026d203e7e Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 20:34:28 +1000 Subject: [PATCH 368/457] add compile workflow --- workflows/Compile.yml | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 workflows/Compile.yml diff --git a/workflows/Compile.yml b/workflows/Compile.yml new file mode 100644 index 000000000..672cb267b --- /dev/null +++ b/workflows/Compile.yml @@ -0,0 +1,58 @@ +name: .NET Core Desktop + +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] + configuration: [Debug] + + 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. + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v1.0.2 + + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: | + nuget restore + msbuild $env:Solution_Name -p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.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()}} \ No newline at end of file From 45deca90c68d2c8eaaeff01640420fb50f032189 Mon Sep 17 00:00:00 2001 From: Urglefloggah666 Date: Mon, 18 Sep 2023 20:49:55 +1000 Subject: [PATCH 369/457] Create build.yml add build workflow/action --- .github/workflows/build.yml | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..cd21bb245 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,58 @@ +name: .NET Core Desktop + +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] + configuration: [Debug] + + 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. + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v1.0.2 + + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: | + nuget restore + msbuild $env:Solution_Name -p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.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()}} From 11f58c886ae4557ae597d9f7d6a3b1be543101d0 Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 20:57:49 +1000 Subject: [PATCH 370/457] remove compile.yml --- workflows/Compile.yml | 58 ------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 workflows/Compile.yml diff --git a/workflows/Compile.yml b/workflows/Compile.yml deleted file mode 100644 index 672cb267b..000000000 --- a/workflows/Compile.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: .NET Core Desktop - -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] - configuration: [Debug] - - 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. - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - # Install the .NET Core workload - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.0.2 - - - # Restore the application to populate the obj folder with RuntimeIdentifiers - - name: Restore the application - run: | - nuget restore - msbuild $env:Solution_Name -p:Configuration=$env:Configuration - env: - Configuration: ${{ matrix.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()}} \ No newline at end of file From e4dbc45a953225b1c0acc809a5066366900bfaca Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 21:06:27 +1000 Subject: [PATCH 371/457] Resolve LazyDictionary.cs unexpected characters --- src/Perpetuum/Collections/LazyDictionary.cs | Bin 5026 -> 4834 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Collections/LazyDictionary.cs b/src/Perpetuum/Collections/LazyDictionary.cs index b0bbc9d828843d7156a81bd32f2a661d8a0588cc..7fa1a88a443b7e075d0c7e11d9b51304939130b5 100644 GIT binary patch delta 582 zcmZ3a{z!F#%tSepiE>60+k_aoCeC$a++{K5*5bens{3yO{30Fj0RUIJ2AS?k4Ld zUOPo7a^maEIcIL`{P}%P1x+cZ8&dR{Djb!R(JdX*OL{?dS9^3qbDH6k(HKumag^iP zxTkt_4m&xvJ6Ia8t+4-`u7D+j*=yE3B~+K3fw*7oSDss`r#)37tWo9JXVzg^-OSaw zAMKD|BVs%`m7btEbmto7+4(PG*Su1B?)#_@5z{#)b%jYgk5pwyBPh&S!Tr(9ZC1JT z&ZamsK?nF5)#dif9G8iY#~!&jy2`Fk;B^cAmiYZYGRsHYRaQyVId6GIQbw#AZlED& z&I(&x#Td`Wc;3u-nfUU%@8q}LBSptx#dt;d5xjUs7+N{3f@j5VJ7ysaU2@#)HD(Q- zJg&wSeTM>7RQ1Ni(d~SKMliIi-0ev&Yc^rI`R<`m+wvZA=(%k;G6L6UJH|Gb{mS3u z9mB18bbSon#MCkzpl?*`b*5dPZ)Y+q%zcWxjr*SI>)yQ1o9j`B*k*#@>@FjG#wC5j z%BY~>R=tmcJ?(IvZn*9ovfk6avmy=tM`QoX;5WYu h-9L+G*0cBM_mC`YoiS5;^!#jH-@6z~9+@aV&|hu<$kYG; From f2c72b6b9286bd87ed1c3382c816a487b4b93199 Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 21:10:08 +1000 Subject: [PATCH 372/457] Fix Kernal.cs characters --- src/Perpetuum/Services/TechTree/Kernel.cs | Bin 540 -> 516 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Services/TechTree/Kernel.cs b/src/Perpetuum/Services/TechTree/Kernel.cs index 43616b572aba0d173efbcfa074e6274f112bfbe8..07237d2ce498f81615cbbe11051e608a6762aadb 100644 GIT binary patch delta 104 zcmbQk(!w&qZlZ%8BiF=YUsf)LY6h;!@{HmWAEPcGuYxIF L%YYCAv1=Is;Y|;& delta 104 zcmZo+nZq){j*)kwtv)L+0~Z7D#9Uua5Tlv_NKO`H6rXrajUObS0EIw_$%Tx Date: Mon, 18 Sep 2023 21:11:08 +1000 Subject: [PATCH 373/457] Fix IConverter.cs unknown characters --- src/Perpetuum/Converters/IConverter.cs | Bin 270 -> 258 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Converters/IConverter.cs b/src/Perpetuum/Converters/IConverter.cs index c5cddff5488be3c3a81260d764a8d4040ad9d444..7a1ba99b47375449b5d64bc9128815a5f1f7044f 100644 GIT binary patch delta 45 mcmeBUYGRsT$IQi0J<&Cln~OmK3acmXbQC~vYZSLN<$Ii>Z#Zb+_JJB(fAH-CELXgl#M-gP9S_TlWmH_|`qX;bk From f656aa2604ef44e7b39dbbfe86d1827e1c4cdfcd Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 21:13:41 +1000 Subject: [PATCH 374/457] update build name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd21bb245..29add46e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: .NET Core Desktop +name: OpenPerpetuum Server Compile on: push: From 0706683cbd69dce2e7812094687546dcbd1a0d80 Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 21:30:09 +1000 Subject: [PATCH 375/457] attempt #2 at fixing LazyDictionary.cs unknown character issue --- src/Perpetuum/Collections/LazyDictionary.cs | Bin 4834 -> 2416 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Collections/LazyDictionary.cs b/src/Perpetuum/Collections/LazyDictionary.cs index 7fa1a88a443b7e075d0c7e11d9b51304939130b5..105309e5cae46dabc79c228ce6fb1e56860a4f25 100644 GIT binary patch literal 2416 zcmbVO&5oNe5We#ib4;Zu574AY745FjR_baS^%6yu1EZ}~up`@~rK`MqZDYVJYoOr* z5zO~9GoDE$DSL)*wNzMcqssfWNqA9UrYL7J{AP2H3M-*pNRaN>J z(MtsSI#;KHW{?#`O87$l)}N^5Afn#wAFy7-{yQlujNv_`p?4Ok!LyixUWh^vNOI1K z+T!kl(J}nUJ5nv&K$y!(8;V-pRVY?sPrnsozb%4D>1MiW-2VnSnPUdP1z3s5C zBi-p|R+T7-*09rwv{`&4R1DJ^!#%40%T-9N&;=CC7rn8Fy5NKGHx+qOpnX0S_&T;T zGfb4DUP9zbcHuOKk9ocd`v$5zJY?1@jdN+hY{Z-XeA=WyB7$igBfIaXa8H~UkZ`65 zW%5=}!2))`3;pqPz2_4u4o4tD5PfnfQ_W+_a{RMA*-1m;Q0%Qim`&0Xmi&Sf;wH-; zOp$|~O^`91@QSHGCqJWt7d@5&N6Ch6;o3!y54CNDR6gm+_Ek@|?#tncnc?Z<7m&5> zp^wta3}pC)!I3uMtZ7~YV&bO?*&W`Ntz7|dF^gb%8<{h!$<{7ziqodmRk@`N5d}@U z_&I-;YcFa&&+G*4vbqLdDYx#Xy#^)tu-3XkY|=Jmzge-Meo}d8?H_OIt%GJg`dwu< zowRK~8h!R#z51*^KW5zaY5(WLuCEv#u$DI|+?dG!V6!!f!QO6UY+{F;CNJ0M`VUB} B!S?_F literal 4834 zcmd5=U60a06ur;AV%A;Ltce>Xz7S$|p;?J+A=@rm$^LcqoR-rFU%vIcs!`7CkoEwIwM!ZwG+ z3?C&QaP$kD-<8)ij~gHO5_dBUtx>ApEH>1_zX@eu!0{( zKmkR9<7uhw95@W#L4a?f{t9bU!Dh;+8Ds`cUA28mMREyUQrHc}Gd{YrJulD>H!Iu9~^jHn)C_@yisI{nNW=wabz^ z3mc0$t7GV%NuR*x9;&Ud|NpbdM%z7ubcC6mTtVcHX}Na%rYGqgRAYv$v4zz{<@F5D zb1G}Qdf#SJo30R}6HpY{iV;`DUQ~h4M7nYS;2MTVF>0k+l7Hji2E) zL? Date: Mon, 18 Sep 2023 21:33:08 +1000 Subject: [PATCH 376/457] Attempt #2 at fixing Kernal.cs unexpected characters --- src/Perpetuum/Services/TechTree/Kernel.cs | Bin 516 -> 257 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Services/TechTree/Kernel.cs b/src/Perpetuum/Services/TechTree/Kernel.cs index 07237d2ce498f81615cbbe11051e608a6762aadb..1870bc56989f779b304b2ecf1bcb7ad576133294 100644 GIT binary patch literal 257 zcmZ9HO%8%E5QX1gj?FmcMcLdgB^MWKdpi27Ut11pe992gmfFMAv1I$bOw72 zbfE0CjyKUx_{E#i`k3#alC40N{-9j-h%^x|AF!wTs4pSf(Tm=e6}qCi7;M?B<_{@R BQPKba literal 516 zcmaKpT?)c55QOKsr^pFBfWGR3_+Y_az@MN(t0hfE5wEU(TdWo>vZUEgW_LD|$Guji zy$;&SDV0}H$v3Jd3Jo;T8RQ5a!8giuX&9A8O5psjo;FnlEMq6=Ea~d`FUi(q{i+L2 zf=*0koQgRz_O*8Gfy~qE%5twLnPjEB;X8Oa-7CUr1~;JV!JLMycBW5tw^M(|Z}k*M z%(eLT!|w5oeH7ZReM;@wmKh`SU0HTVdjp%?@djfYnB!SQ^$2E;bZ_>nJ)-W`ai~@4 IVwT_Z1XI>i&j0`b From 74a8b139d16a434014094982bb914aadd7becd2d Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 21:35:17 +1000 Subject: [PATCH 377/457] Attempt #2 for IConverter and IWallet files unknown characters --- src/Perpetuum/Converters/IConverter.cs | Bin 258 -> 128 bytes src/Perpetuum/Wallets/IWallet.cs | Bin 256 -> 120 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Converters/IConverter.cs b/src/Perpetuum/Converters/IConverter.cs index 7a1ba99b47375449b5d64bc9128815a5f1f7044f..444e3e0f7b202f51e366dab7925ddbe0f3d4c2a3 100644 GIT binary patch literal 128 zcmc~x%uOvWNK8&u2uLj|NG&NX&DC?x&nrtUDoHIW=BnmW0D^+jq@2uTh0HvlSQ<#J zCtQh5W}ZTbXP%Bierbt9h<|B`9aslMA4o4qOaZD~1E^9Vvm`ZF(;BR*ma7&35#1=t literal 258 zcmZvWK?=e!5JlhGQ_NC9yny1yRad&`1*}vMOj>MoQ+jplPiQ5G4EgyXf8Naa_MCCl zbhL68IpZXK=v@}41 vh`ddRb5UwaW{DkG6+|saJycl1DKRH852(CaAw9LkTA>(3)PmL4a@7I=W$h)8 literal 256 zcmY+8K?;LF3`O6%;2q`!y+EZ4yXvkBp=+&UDO3^1uEeX`{u!wd0^uk5dC7OpIM7qk z;Ry^<4=Kk@c~tG0_$WG#+%X?N Date: Mon, 18 Sep 2023 21:38:32 +1000 Subject: [PATCH 378/457] fix IConvertible.cs unknown character issue --- src/Perpetuum/Converters/IConvertible.cs | Bin 238 -> 111 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Perpetuum/Converters/IConvertible.cs b/src/Perpetuum/Converters/IConvertible.cs index 8d88cec3ac7c617552bafab6b9cf34c31f190e00..175c60b6e2d4be74f5f9d9415b518b08684535ac 100644 GIT binary patch literal 111 zcmdQY7O@3*KLWmt$Aw)SyONatgNl3nirZrfyma7&3MdKpG literal 238 zcmZ9GJqp4=5QV>5@D5v=#tS6aSlOl63#e=$ki=kqnvkm--ws4r8FROGs6}$pPK3y+e(c*HsWyM0 d5Py=};khKt^um70%bEVC+XFul`pO Date: Mon, 18 Sep 2023 21:55:00 +1000 Subject: [PATCH 379/457] update build file to upload all built files, maybe... --- .github/workflows/build.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29add46e0..9ea1908a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,8 +51,9 @@ jobs: msbuild $env:Solution_Name -p:Configuration=$env:Configuration env: Configuration: ${{ matrix.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 + uses: actions/upload-artifact@v3 + with: + name: First-Perp-Artifact + path: D:\a\PerpetuumServer\PerpetuumServer\bin\* From 2e5f4538ac75873415a1e9d52a389a3aab5b992f Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 22:02:03 +1000 Subject: [PATCH 380/457] change build to do release instead of debug --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ea1908a1..9507426a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: # configuration: [Debug, Release] - configuration: [Debug] + configuration: [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 From b2539163142d83fe6b1c5576e5b557dba262129c Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 22:15:58 +1000 Subject: [PATCH 381/457] update the artifact upload --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9507426a4..00a97a1e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,5 +55,5 @@ jobs: - name: Upload a Build Artifact uses: actions/upload-artifact@v3 with: - name: First-Perp-Artifact - path: D:\a\PerpetuumServer\PerpetuumServer\bin\* + name: Perpetuum-Server + path: .\PerpetuumServer\bin\x64\* From c347be46676965fc47eee82887b1af5fc68998ef Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 22:19:16 +1000 Subject: [PATCH 382/457] removed too much path --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00a97a1e2..7e2c2d695 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,4 +56,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: Perpetuum-Server - path: .\PerpetuumServer\bin\x64\* + path: .\PerpetuumServer\PerpetuumServer\bin\x64\* From a625e5000591f3a6c342a4d0e23698476a5db2aa Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 22:21:33 +1000 Subject: [PATCH 383/457] add back in TMate --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e2c2d695..e27be3e71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,6 +51,12 @@ jobs: msbuild $env:Solution_Name -p:Configuration=$env:Configuration env: Configuration: ${{ matrix.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 uses: actions/upload-artifact@v3 From 96de5d3a5a51deb29221cf05dc80ac764a4db1c7 Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 22:25:26 +1000 Subject: [PATCH 384/457] add back in path because i cant tell where its running from --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e27be3e71..85110cfc5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,7 +51,7 @@ jobs: msbuild $env:Solution_Name -p:Configuration=$env:Configuration env: Configuration: ${{ matrix.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 @@ -62,4 +62,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: Perpetuum-Server - path: .\PerpetuumServer\PerpetuumServer\bin\x64\* + path: D:\a\PerpetuumServer\PerpetuumServer\bin\x64\* From b9d4550df694db14849f7fb277e65d139bb4c957 Mon Sep 17 00:00:00 2001 From: Igneous Date: Mon, 18 Sep 2023 14:26:14 +0200 Subject: [PATCH 385/457] Update MissionProcessorDeliverMission.cs Removed unused var deliveryState in MissionProcessorDeliverMission.cs --- .../MissionProcessorObjects/MissionProcessorDeliverMission.cs | 2 -- 1 file changed, 2 deletions(-) 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); From 5c50fd3279f9a3258b3191a607b6d21e49ed244c Mon Sep 17 00:00:00 2001 From: "DESKTOP-8LUE5OF\\Admin" Date: Sun, 29 Oct 2023 19:47:31 +0500 Subject: [PATCH 386/457] - Removed undock availability check between using 2 spark teleports in a row --- src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs index fba336b89..06ae91714 100644 --- a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs +++ b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs @@ -32,7 +32,7 @@ public void HandleRequest(IRequest request) var character = request.Session.Character; character.IsDocked.ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); - character.CheckNextAvailableUndockTimeAndThrowIfFailed(); + //character.CheckNextAvailableUndockTimeAndThrowIfFailed(); //CheckExtensionLevelAndThrowIfFailed(character); var sparkTeleport = _sparkTeleportHelper.GetCommon(id);//.Get(id); From bb847a324b15e84763f5118e135044e2a77c9f3c Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Tue, 26 Sep 2023 00:52:43 +0300 Subject: [PATCH 387/457] Prototype lines - Advanced and Expert prototype parallel processing are now enabled --- .../Services/ProductionEngine/Facilities/Prototyper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs index 1874c70d8..b9fd22aa8 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) From 2d5b4af62a74ba7a2e1f20d92d8dd79c46522095 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 27 Sep 2023 17:59:01 +0300 Subject: [PATCH 388/457] Gamma corp hangar fix - Added check that Armor and ArmorMax are greater than zero (i.e. initialized) when calculating health --- src/Perpetuum/Robots/Robot.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 8804f83f3..0d9d9776d 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -136,7 +136,9 @@ public override double Health return base.Health; } - return Armor.Ratio(ArmorMax) * 100; + return ArmorMax > 0.0 && Armor > 0.0 + ? Armor.Ratio(ArmorMax) * 100 + : base.Health; } } From 16e6461879301c9fd277aae4edea296d53307d96 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Tue, 29 Aug 2023 20:01:47 +0300 Subject: [PATCH 389/457] Deadly Toys: Landmines - Initial commit. Contains working mechanic for ladmines - can buy on parket and deploy, they will explode on target stays in 50m radius for 2 seconds. There is no way to kill them but they will autodespawn in 8 hours (or whatever, same value for probes) --- .../PerpetuumBootstrapper.cs | 28 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 10 +- .../ProximityProbeGetRegistrationInfo.cs | 2 +- .../ProximityProbeList.cs | 6 +- .../ProximityProbeRegisterSet.cs | 2 +- .../ProximityProbeSetName.cs | 2 +- .../Zone/ProximityProbeRemove.cs | 2 +- .../EntityFramework/DefinitionConfig.cs | 1 - .../Corporations/Corporation.Helpers.cs | 13 + src/Perpetuum/Keywords.cs | 2 + src/Perpetuum/Perpetuum.csproj | 9 +- src/Perpetuum/Players/Player.cs | 3 +- src/Perpetuum/Zones/DistanceConstants.cs | 6 + .../PBSRegisterHelper.cs | 0 src/Perpetuum/Zones/LandMines/LandMine.cs | 103 ++++++ .../Zones/LandMines/LandMineDeployer.cs | 82 +++++ .../Zones/ProximityProbes/ProximityDevice.cs | 337 ++++++++++++++++++ .../Zones/ProximityProbes/ProximityProbe.cs | 306 +--------------- .../ProximityProbes/ProximityProbeDeployer.cs | 12 +- .../VisibilityBasedProximityProbe.cs | 60 ---- src/Perpetuum/Zones/Zone.cs | 3 +- src/Perpetuum/Zones/ZoneExtensions.Unit.cs | 5 +- 22 files changed, 617 insertions(+), 377 deletions(-) rename src/Perpetuum/Zones/{ProximityProbes => Helpers}/PBSRegisterHelper.cs (100%) create mode 100644 src/Perpetuum/Zones/LandMines/LandMine.cs create mode 100644 src/Perpetuum/Zones/LandMines/LandMineDeployer.cs create mode 100644 src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs delete mode 100644 src/Perpetuum/Zones/ProximityProbes/VisibilityBasedProximityProbe.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 1dbee450b..a6c160894 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -187,6 +187,7 @@ using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; using Perpetuum.Items.Helpers; +using Perpetuum.Zones.LandMines; namespace Perpetuum.Bootstrapper { @@ -798,7 +799,7 @@ protected void RegisterCorporation() where T : Corporation RegisterEntity().PropertiesAutowired(); } - protected void RegisterProximityProbe() where T : ProximityProbeBase + protected void RegisterProximityDevices() where T : ProximityDeviceBase { RegisterUnit(); } @@ -906,7 +907,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))); @@ -1017,7 +1021,10 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); - RegisterEntity(); + + RegisterEntity(); + RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -1241,7 +1248,10 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_landmine); + ByCategoryFlags(CategoryFlags.cf_random_research_kits); ByCategoryFlags(CategoryFlags.cf_lottery_items); ByCategoryFlags(CategoryFlags.cf_paints); // OPP Robot paint! @@ -1253,7 +1263,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] // OPP new Blinder module ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); - ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); + ByCategoryFlags(CategoryFlags.cf_proximity_probe_deployer); + ByCategoryFlags(CategoryFlags.cf_landmine_deployer); + ByCategoryFlags(CategoryFlags.cf_gift_packages); ByCategoryFlags(CategoryFlags.cf_pbs_capsules); ByCategoryFlags(CategoryFlags.cf_pbs_egg); @@ -1844,11 +1856,13 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.SparkList); RegisterRequestHandler(Commands.SparkSetDefault); RegisterRequestHandler(Commands.SparkUnlock); - RegisterRequestHandler(Commands.Undock); + RegisterRequestHandler(Commands.Undock) + ; RegisterRequestHandler(Commands.ProximityProbeRegisterSet); RegisterRequestHandler(Commands.ProximityProbeSetName); RegisterRequestHandler(Commands.ProximityProbeList); RegisterRequestHandler(Commands.ProximityProbeGetRegistrationInfo); + RegisterRequestHandler(Commands.IntrusionEnabler); RegisterRequestHandler(Commands.AccountGetTransactionHistory); RegisterRequestHandler(Commands.AccountList); @@ -2828,7 +2842,9 @@ private void RegisterZoneRequestHandlers() RegisterZoneRequestHandler(Commands.GetRifts); RegisterZoneRequestHandler(Commands.UseItem); RegisterZoneRequestHandler(Commands.GateSetName); + RegisterZoneRequestHandler(Commands.ProximityProbeRemove); + RegisterZoneRequestHandler(Commands.FieldTerminalInfo); RegisterZoneRequestHandler(Commands.PBSFeedableInfo); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 417076cac..99995d898 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -501,8 +501,10 @@ public enum CategoryFlags : long 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, @@ -643,7 +645,13 @@ public enum CategoryFlags : long cf_tutorial_industry_items = 0x0000000000000295, cf_tutorial_punchbag = 0x0000000000000496, cf_vendors = 0x000000000000026E, - cf_visibility_based_probe = 0x0000000002010492, + + cf_proximity_probe = 0x0000000002010492, + + cf_traps = 0x00000000040492, + cf_landmine = 0x0000000001040492, + cf_landmine_deployer = 0x0000000000000D98, + cf_volume_wrapper_container = 0x0000000000000B15, cf_volume_wrapper_container_old = 0x0000000000080815, cf_walker_calibration_programs = 0x0000000005010416, 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/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/EntityFramework/DefinitionConfig.cs b/src/Perpetuum/EntityFramework/DefinitionConfig.cs index 8f01bde5e..bfce9500f 100644 --- a/src/Perpetuum/EntityFramework/DefinitionConfig.cs +++ b/src/Perpetuum/EntityFramework/DefinitionConfig.cs @@ -142,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)) diff --git a/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs b/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs index c53966fa1..4870ccbee 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,17 @@ public IEnumerable GetProximityProbeEids() .Select(r => r.GetValue(0)) .ToArray(); } + + // TODO: Find a way to use joins instead of this + public IEnumerable GetLandMineEids() + { + var landMineDefinitions = EntityServices.Defaults.GetAll().GetDefinitionsByCategoryFlag(CategoryFlags.cf_landmine); + 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/Keywords.cs b/src/Perpetuum/Keywords.cs index 98fd73323..acda264a0 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1165,6 +1165,8 @@ public class k public const string wearLevelMax_others = "wearLevelMax_others"; public const string wearLevelMin_others = "wearLevelMin_others"; + //public const string landmine_trigger_range = "landMineTriggerRange"; + public const string weather = "weather"; public const string weight = "weight"; public const string width = "width"; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index bf84bf6e8..1aa0983f9 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -714,6 +714,8 @@ + + @@ -757,8 +759,8 @@ - - + + @@ -1078,7 +1080,7 @@ - + @@ -1262,6 +1264,7 @@ + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index cb6dbf829..40dd7f62b 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -38,6 +38,7 @@ using Perpetuum.Zones.Effects; using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.LandMines; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; @@ -754,7 +755,7 @@ private bool IsUnitPVPAggro(Unit unit) return unit is MobileTeleport || unit is IPBSObject || unit is WallHealer - || unit is ProximityProbeBase + || unit is ProximityDeviceBase || (unit is BlobEmitterUnit b && b.IsPlayerSpawned); } diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index aa0e8271c..8546248e2 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -29,6 +29,10 @@ public class DistanceConstants 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; @@ -51,6 +55,8 @@ public class DistanceConstants public const double PBS_DIST_FROM_NPC_BASE = 300; public const double GAMMA_BOMB_STACK_DISTANCE = 5.0; + public const double LANDMINE_DEPLOY_DISTANCE = 1.0; + public const double PBS_DIST_FROM_TELEPORT = 200; public static Dictionary GetEnumDictionary() 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/LandMines/LandMine.cs b/src/Perpetuum/Zones/LandMines/LandMine.cs new file mode 100644 index 000000000..7945c8d6d --- /dev/null +++ b/src/Perpetuum/Zones/LandMines/LandMine.cs @@ -0,0 +1,103 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Log; +using Perpetuum.Modules.Weapons; +using Perpetuum.Players; +using Perpetuum.Robots; +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; +using System.Security.Policy; + +namespace Perpetuum.Zones.LandMines +{ + public class LandMine : ProximityDeviceBase + { + private const int BeamDistance = 600; + private readonly IntervalTimer probingInterval = new IntervalTimer(TimeSpan.FromSeconds(2)); + + protected override void OnUpdate(TimeSpan time) + { + var despawnHelper = GetDespawnHelper(); + + probingInterval.Update(time); + + if (!probingInterval.Passed) + return; + + probingInterval.Reset(); + + if (IsActive) + { + //detect + var robotsNearMe = GetNoticedUnits(); + + if (robotsNearMe.Any()) + { + robotsNearMe[0].Zone.CreateBeam( + BeamType.plant_bomb_explosion,//.timebomb_explosion, + builder => builder + .WithPosition(CurrentPosition) + .WithState(BeamState.Hit) + .WithVisibility(BeamDistance)); + + // There be explosion + var 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); + + robotsNearMe[0].Zone.DoAoeDamageAsync(damageBuilder); + + OnDead(robotsNearMe[0]); + } + } + + if (despawnHelper == null) + { + var m = GetPropertyModifier(AggregateField.despawn_time); + var timespan = TimeSpan.FromMilliseconds((int)m.Value); + SetDespawnTime(timespan); + despawnHelper = GetDespawnHelper(); + } + + despawnHelper.Update(time, this); + } + + #region probe functions + + // egy adott pillanatban kiket lat + [CanBeNull] + public override List GetNoticedUnits() + { + return GetVisibleUnits().Select(v => v.Target).OfType().ToList(); + } + protected override bool IsDetected(Unit target) + { + var 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); + } + + #endregion + } +} diff --git a/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs b/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs new file mode 100644 index 000000000..d1ec462e5 --- /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_DISTANCE).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/ProximityProbes/ProximityDevice.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs new file mode 100644 index 000000000..bfe311697 --- /dev/null +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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; + +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 this._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 + var robotsNearMe = GetNoticedUnits(); + + //do something + OnUnitsFound(robotsNearMe); + } + + if (_despawnHelper == null) + { + var m = GetPropertyModifier(AggregateField.despawn_time); + var 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; + + 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) + { + OnDeviceDead(); + base.OnDead(killer); + } + + public Dictionary GetProbeInfo(bool includeRegistered = true) + { + var info = BaseInfoToDictionary(); + + var 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() + { + 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 SendDeviceDead() + { + 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) + { + 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 => + { + 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() + { + var 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; + } + + var corporationEid = character.CorporationEid; + + if (corporationEid != Owner) + { + return ErrorCodes.AccessDenied; + } + + var role = Corporation.GetRoleFromSql(character); + + if (IsAllProbesVisible(role)) + { + return ErrorCodes.NoError; + } + + 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); + } + + 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 796845818..07654479d 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs @@ -1,320 +1,48 @@ -using System; using System.Collections.Generic; using System.Linq; -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 Perpetuum.Zones.Blobs.BlobEmitters; 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)); - private UnitDespawnHelper _despawnHelper; - - protected ProximityProbeBase() - { - _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; - } - 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 - var robotsNearMe = GetNoticedUnits(); - - //do something - OnUnitsFound(robotsNearMe); - } - - if (_despawnHelper == null) - { - var m = GetPropertyModifier(AggregateField.despawn_time); - var 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(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) - { - //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) + public override List GetNoticedUnits() { - OnProbeDead(); - base.OnDead(killer); + return GetVisibleUnits().Select(v=>v.Target).OfType().ToList(); } - public Dictionary GetProbeInfo(bool includeRegistered = true) + protected override bool IsActive { - var info = BaseInfoToDictionary(); - - var probeDict = new Dictionary(); - - if (includeRegistered) + get { - probeDict.Add(k.registered, GetRegisteredCharacters().GetCharacterIDs().ToArray()); + var coreRatio = Core.Ratio(CoreMax); + return coreRatio > 0.98; } - - 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() + public double BlobEmission { - 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) - { - 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; + var blobEmission = GetPropertyModifier(AggregateField.blob_emission); + return blobEmission.Value; } - - return ((int) config.cycle_time) + FastRandom.NextInt(0,250); } - public bool IsRegistered(Character character) + public double BlobEmissionRadius { - return GetRegisteredCharacters().Contains(character); - } - - public ErrorCodes HasAccess(Character character) - { - if (IsRegistered(character)) - { - return ErrorCodes.NoError; - } - - var corporationEid = character.CorporationEid; - - if (corporationEid != Owner) + get { - return ErrorCodes.AccessDenied; + var blobEmissionRadius = GetPropertyModifier(AggregateField.blob_emission_radius); + return blobEmissionRadius.Value; } - - var role = Corporation.GetRoleFromSql(character); - - if (IsAllProbesVisible(role)) - { - return ErrorCodes.NoError; - } - - 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 9c2db44d3..7a55ece82 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbeDeployer.cs @@ -9,13 +9,13 @@ 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) @@ -27,7 +27,7 @@ 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(); @@ -39,7 +39,7 @@ 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; } 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/Zone.cs b/src/Perpetuum/Zones/Zone.cs index 983193fa2..9858f5e82 100644 --- a/src/Perpetuum/Zones/Zone.cs +++ b/src/Perpetuum/Zones/Zone.cs @@ -27,6 +27,7 @@ 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; @@ -126,7 +127,7 @@ public override void Stop() SaveUnitsToDb(); SaveUnitsToDb(); SaveUnitsToDb(); - SaveUnitsToDb(); + SaveUnitsToDb(); } private void OnGangMemberJoined(Gang gang, Character character) diff --git a/src/Perpetuum/Zones/ZoneExtensions.Unit.cs b/src/Perpetuum/Zones/ZoneExtensions.Unit.cs index 03698e0e6..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; @@ -43,7 +44,7 @@ public static Dictionary GetBuildingsDictionaryForCharacter(this { var count = 0; var buildingsDict = zone.Units - .NotOf().Types().Type() + .NotOf().Types().Type() .Where(u => { if (u is PBSDockingBase pbsDockingBase) @@ -79,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] From 71c03bfb603613bc6ea5f3b733d44d7afdc60b03 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 19:23:28 +0300 Subject: [PATCH 390/457] Landmines - Just small changes --- src/Perpetuum/EntityFramework/DefinitionConfig.cs | 1 + src/Perpetuum/Keywords.cs | 2 -- src/Perpetuum/Zones/DistanceConstants.cs | 2 -- src/Perpetuum/Zones/LandMines/LandMineDeployer.cs | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Perpetuum/EntityFramework/DefinitionConfig.cs b/src/Perpetuum/EntityFramework/DefinitionConfig.cs index bfce9500f..8f01bde5e 100644 --- a/src/Perpetuum/EntityFramework/DefinitionConfig.cs +++ b/src/Perpetuum/EntityFramework/DefinitionConfig.cs @@ -142,6 +142,7 @@ 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)) diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index acda264a0..98fd73323 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1165,8 +1165,6 @@ public class k public const string wearLevelMax_others = "wearLevelMax_others"; public const string wearLevelMin_others = "wearLevelMin_others"; - //public const string landmine_trigger_range = "landMineTriggerRange"; - public const string weather = "weather"; public const string weight = "weight"; public const string width = "width"; diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index 8546248e2..fa743d32c 100644 --- a/src/Perpetuum/Zones/DistanceConstants.cs +++ b/src/Perpetuum/Zones/DistanceConstants.cs @@ -55,8 +55,6 @@ public class DistanceConstants public const double PBS_DIST_FROM_NPC_BASE = 300; public const double GAMMA_BOMB_STACK_DISTANCE = 5.0; - public const double LANDMINE_DEPLOY_DISTANCE = 1.0; - public const double PBS_DIST_FROM_TELEPORT = 200; public static Dictionary GetEnumDictionary() diff --git a/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs b/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs index d1ec462e5..b52c3e4a3 100644 --- a/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs +++ b/src/Perpetuum/Zones/LandMines/LandMineDeployer.cs @@ -63,7 +63,7 @@ protected override ErrorCodes CanDeploy(IZone zone, Unit unit, Position spawnPos } else { - if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_DISTANCE).Any()) + if (zone.Units.OfType().WithinRange(spawnPosition, DistanceConstants.LANDMINE_DEPLOY_RANGE_FROM_LANDMINE).Any()) return ErrorCodes.TooCloseToOtherDevice; } From e62832b32fea1baaf13b0602765dcf3cb112abae Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Sun, 24 Sep 2023 02:54:12 +0300 Subject: [PATCH 391/457] Landmines - Completed mechanics --- .../PerpetuumBootstrapper.cs | 6 +- src/Perpetuum.ExportedTypes/AggregateField.cs | 7 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1333 +++++++++-------- src/Perpetuum.ExportedTypes/EffectCategory.cs | 3 +- src/Perpetuum.ExportedTypes/EffectType.cs | 3 +- .../EntityFramework/DefinitionConfig.cs | 1 - .../Corporations/Corporation.Helpers.cs | 10 +- .../EffectModules/MineDetectorModule.cs | 31 + src/Perpetuum/Perpetuum.csproj | 1 + src/Perpetuum/Robots/Robot.Properties.cs | 89 +- src/Perpetuum/Robots/Robot.cs | 2 +- src/Perpetuum/Units/Unit.Visibility.cs | 17 +- src/Perpetuum/Zones/LandMines/LandMine.cs | 12 +- src/Perpetuum/Zones/NpcSystem/Creature.cs | 3 +- 14 files changed, 811 insertions(+), 707 deletions(-) create mode 100644 src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index a6c160894..a2cbf76c5 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -979,6 +979,7 @@ private void RegisterEntities() RegisterEffectModule(); RegisterEffectModule(); RegisterEffectModule(); + RegisterEffectModule(); RegisterEntity(); RegisterEntity(); @@ -1202,6 +1203,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); @@ -1250,7 +1252,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_plasma_bomb); ByCategoryFlags(CategoryFlags.cf_proximity_probe); - ByCategoryFlags(CategoryFlags.cf_landmine); + 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); diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index feaaef482..6f224399f 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -227,7 +227,12 @@ public enum AggregateField 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, + 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, diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 99995d898..555b44290 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -9,671 +9,694 @@ namespace Perpetuum.ExportedTypes 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_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_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_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_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_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_mining_upgrades = 0x000000000004060F, + cf_mining_probes = 0x000000000005060F, + cf_harvesters = 0x000000000006060F, + cf_small_harvesters = 0x000000000106060F, + cf_medium_harvesters = 0x000000000206060F, + cf_medium_harvesters_old = 0x000000000007060F, + 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_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_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_ct_capsules = 0x000000000003049B, - cf_ct_capsules_ammo = 0x000000000103049B, - cf_ct_capsules_ammo_laser = 0x000000010103049B, - cf_ct_capsules_ammo_missile = 0x000000040103049B, - cf_ct_capsules_ammo_projectile = 0x000000030103049B, - cf_ct_capsules_ammo_railgun = 0x000000020103049B, - cf_ct_capsules_equipment = 0x000000000203049B, - cf_ct_capsules_equipment_armor = 0x000000010203049B, - cf_ct_capsules_equipment_electronics = 0x000000030203049B, - cf_ct_capsules_equipment_engineering = 0x000000040203049B, - cf_ct_capsules_equipment_ewar = 0x000000020203049B, - cf_ct_capsules_equipment_industrial = 0x000000050203049B, - cf_ct_capsules_equipment_nexus = 0x000000080203049B, - cf_ct_capsules_equipment_shield = 0x000000060203049B, - cf_ct_capsules_equipment_weapons = 0x000000070203049B, - cf_ct_capsules_misc = 0x000000000403049B, - cf_ct_capsules_robot = 0x000000000303049B, - cf_ct_capsules_robot_industrial = 0x000000040303049B, - cf_ct_capsules_robot_industrial_assault = 0x000002040303049B, - cf_ct_capsules_robot_industrial_heavymech = 0x000004040303049B, - cf_ct_capsules_robot_industrial_light = 0x000001040303049B, - cf_ct_capsules_robot_industrial_mech = 0x000003040303049B, - cf_ct_capsules_robot_nuimqol = 0x000000030303049B, - cf_ct_capsules_robot_nuimqol_assault = 0x000002030303049B, - cf_ct_capsules_robot_nuimqol_heavymech = 0x000004030303049B, - cf_ct_capsules_robot_nuimqol_light = 0x000001030303049B, - cf_ct_capsules_robot_nuimqol_mech = 0x000003030303049B, - cf_ct_capsules_robot_pelistal = 0x000000010303049B, - cf_ct_capsules_robot_pelistal_assault = 0x000002010303049B, - cf_ct_capsules_robot_pelistal_heavymech = 0x000004010303049B, - cf_ct_capsules_robot_pelistal_light = 0x000001010303049B, - cf_ct_capsules_robot_pelistal_mech = 0x000003010303049B, - cf_ct_capsules_robot_syndicate = 0x000000050303049B, - cf_ct_capsules_robot_syndicate_assault = 0x000002050303049B, - cf_ct_capsules_robot_syndicate_heavymech = 0x000004050303049B, - cf_ct_capsules_robot_syndicate_light = 0x000001050303049B, - cf_ct_capsules_robot_syndicate_mech = 0x000003050303049B, - cf_ct_capsules_robot_thelodica = 0x000000020303049B, - cf_ct_capsules_robot_thelodica_assault = 0x000002020303049B, - cf_ct_capsules_robot_thelodica_heavymech = 0x000004020303049B, - cf_ct_capsules_robot_thelodica_light = 0x000001020303049B, - cf_ct_capsules_robot_thelodica_mech = 0x000003020303049B, - cf_datashards = 0x0000000000000694, - 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_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_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_ep_boosters = 0x000000000001049B, - 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_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_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_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_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_paints = 0x000000000002049B, - 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_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_stealth_modules = 0x00000000000A040F, + 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_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_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_proximity_probe = 0x0000000002010492, + 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_traps = 0x00000000040492, - cf_landmine = 0x0000000001040492, - cf_landmine_deployer = 0x0000000000000D98, + 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_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, - cf_respec_tokens = 0x000000000004049B, - cf_spark_teleport_devices = 0x000000000005049B, + 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_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_misc = 0x000000000403049B, + cf_respec_tokens = 0x000000000004049B, + cf_spark_teleport_devices = 0x000000000005049B, } } diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 8b831bf54..9e7b6d1b3 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -53,6 +53,7 @@ public enum EffectCategory : long effcat_terrain_object_effects = 0x0000080000000000, effcat_zero_core_drop = 0x0000000400000000, effcat_zone_effects = 0x0000100000000000, // OPP: New Zone effects - effcat_environmental_effects = 0x0000200000000000 // OPP: New Zone effects + effcat_environmental_effects = 0x0000200000000000, // OPP: New Zone effects + effcat_mine_detection = 0x0000400000000000, // OPP: New Zone effects } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 97ef7bccd..9316d5787 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -120,6 +120,7 @@ public enum EffectType effect_gamma_t1 = 117, effect_gamma_t2 = 118, effect_gamma_t3 = 119, - effect_gamma_tc = 120 + effect_gamma_tc = 120, + effect_mine_detector = 121, } } diff --git a/src/Perpetuum/EntityFramework/DefinitionConfig.cs b/src/Perpetuum/EntityFramework/DefinitionConfig.cs index 8f01bde5e..bfce9500f 100644 --- a/src/Perpetuum/EntityFramework/DefinitionConfig.cs +++ b/src/Perpetuum/EntityFramework/DefinitionConfig.cs @@ -142,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)) diff --git a/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs b/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs index 4870ccbee..a42a99c78 100644 --- a/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs +++ b/src/Perpetuum/Groups/Corporations/Corporation.Helpers.cs @@ -65,10 +65,16 @@ public IEnumerable GetProximityProbeEids() // TODO: Find a way to use joins instead of this public IEnumerable GetLandMineEids() { - var landMineDefinitions = EntityServices.Defaults.GetAll().GetDefinitionsByCategoryFlag(CategoryFlags.cf_landmine); + 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) + return Db.Query() + .CommandText(queryStr) + .SetParameter("@corporationEID", Eid) .Execute() .Select(r => r.GetValue(0)) .ToArray(); diff --git a/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs b/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs new file mode 100644 index 000000000..df7a98bb3 --- /dev/null +++ b/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs @@ -0,0 +1,31 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 1aa0983f9..5aeb0ad45 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -258,6 +258,7 @@ + diff --git a/src/Perpetuum/Robots/Robot.Properties.cs b/src/Perpetuum/Robots/Robot.Properties.cs index 34f674e9a..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) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 0d9d9776d..819eeb2c4 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -236,7 +236,7 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) { base.OnDamageTaken(source, e); - var decayChance = _decayChance.Value; + var decayChance = this.decayChance.Value; var random = FastRandom.NextDouble(); if (decayChance >= random) diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index 7ce73ab54..e49557b2b 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -7,6 +7,7 @@ using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; +using Perpetuum.Zones.LandMines; namespace Perpetuum.Units { @@ -98,14 +99,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 / Math.Max(1, target.StealthStrength) * Math.Max(1, DetectionStrength); return IsInRangeOf3D(target, range); } diff --git a/src/Perpetuum/Zones/LandMines/LandMine.cs b/src/Perpetuum/Zones/LandMines/LandMine.cs index 7945c8d6d..a90ee51d7 100644 --- a/src/Perpetuum/Zones/LandMines/LandMine.cs +++ b/src/Perpetuum/Zones/LandMines/LandMine.cs @@ -23,6 +23,14 @@ public class LandMine : ProximityDeviceBase private const int BeamDistance = 600; private readonly IntervalTimer probingInterval = new IntervalTimer(TimeSpan.FromSeconds(2)); + public int TriggerMass + { + get + { + return ED.Options.GetOption("triggerMass"); + } + } + protected override void OnUpdate(TimeSpan time) { var despawnHelper = GetDespawnHelper(); @@ -30,7 +38,9 @@ protected override void OnUpdate(TimeSpan time) probingInterval.Update(time); if (!probingInterval.Passed) + { return; + } probingInterval.Reset(); @@ -39,7 +49,7 @@ protected override void OnUpdate(TimeSpan time) //detect var robotsNearMe = GetNoticedUnits(); - if (robotsNearMe.Any()) + if (robotsNearMe.Exists(x => x.ActualMass > this.TriggerMass)) { robotsNearMe[0].Zone.CreateBeam( BeamType.plant_bomb_explosion,//.timebomb_explosion, diff --git a/src/Perpetuum/Zones/NpcSystem/Creature.cs b/src/Perpetuum/Zones/NpcSystem/Creature.cs index 51055949e..87ac9c1cd 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); } From eb9bb442d7c716ab9b6017cfd17e73ef012428d0 Mon Sep 17 00:00:00 2001 From: Ghroom Date: Mon, 27 Sep 2021 11:25:08 +0200 Subject: [PATCH 392/457] Logging - Chat Commands (#315) --- .../Services/Channels/ChannelManager.cs | 12 +++++- .../ChatCommands/GameAdminCommands.cs | 40 ++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index f32ff95ca..fb4b17617 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -266,9 +266,17 @@ public void Talk(string channelName, Character sender, string message, IRequest channel.Logger.LogMessage(sender, message); m.CanTalk.ThrowIfFalse(ErrorCodes.CharacterIsMuted); - channel.SendMessageToAll(_sessionManager, sender, message); - _adminCommand.TryParseAdminCommand(sender, message, request, channel, this); + //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) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index fedf73b90..a7754be4b 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -1,6 +1,7 @@ using Perpetuum.Accounting.Characters; using Perpetuum.Host.Requests; using Perpetuum.Services.Sessions; +using Perpetuum.Data; using System; using System.Collections.Generic; using System.Linq; @@ -30,21 +31,47 @@ public AdminCommandRouter(GlobalConfiguration configuration, ISessionManager ses public void TryParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) { - if (IsAdminCommand(sender, text)) + var b_isAdmin = IsAdmin(sender); + if (b_isAdmin) { + WriteLogToDb(sender, text, b_isAdmin); //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, b_isAdmin); + } + } + + public bool IsAdminCommand(string text) + { + return text.StartsWith("#"); + } + private bool IsAdmin(Character sender) + { + return sender.AccessLevel == AccessLevel.admin; } - private bool IsAdminCommand(Character sender, string message) + private void WriteLogToDb(Character sender, string text, bool badmin) { - return message.StartsWith("#") && sender.AccessLevel == AccessLevel.admin; + var str_trunc = text; + var adminright = sender.AccessLevel; + + if (text.Length > 255) + { + str_trunc = text.Substring(1, 255); //text truncated to avoid big text spam insertion in db + } + + Db.Query().CommandText("insert 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) { - if (!IsAdminCommand(sender, text)) - return; string[] command = text.Split(new char[] { ',' }); @@ -60,6 +87,9 @@ private void ParseAdminCommand(Character sender, string text, IRequest request, // 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; } From aeeaa472f2c6b542eee2806584a0992752751fb7 Mon Sep 17 00:00:00 2001 From: Ghroom Date: Mon, 27 Sep 2021 12:16:05 +0200 Subject: [PATCH 393/457] added dipslay of the command if it's an admin --- .../Services/Channels/ChatCommands/GameAdminCommands.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index a7754be4b..82ce85c8f 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -34,6 +34,7 @@ public void TryParseAdminCommand(Character sender, string text, IRequest request var b_isAdmin = IsAdmin(sender); if (b_isAdmin) { + channel.SendMessageToAll(_sessionManager, sender, text); //in the future, it will be displayed only in the secure channel WriteLogToDb(sender, text, b_isAdmin); //enhancement todo: put the log in the next part to be able to log all the admin details ParseAdminCommand(sender, text, request, channel, channelManager); } From 22ac92c2df364ee810567e6290151988c7cf7fc7 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 19:34:37 +0300 Subject: [PATCH 394/457] CommandLog - Style-guide fixes. also removed unused variables --- .../Channels/ChatCommands/GameAdminCommands.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index 82ce85c8f..b4b1f1385 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -31,16 +31,16 @@ public AdminCommandRouter(GlobalConfiguration configuration, ISessionManager ses public void TryParseAdminCommand(Character sender, string text, IRequest request, Channel channel, IChannelManager channelManager) { - var b_isAdmin = IsAdmin(sender); - if (b_isAdmin) + 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, b_isAdmin); //enhancement todo: put the log in the next part to be able to log all the admin details + 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, b_isAdmin); + WriteLogToDb(sender, text); } } @@ -53,10 +53,9 @@ private bool IsAdmin(Character sender) return sender.AccessLevel == AccessLevel.admin; } - private void WriteLogToDb(Character sender, string text, bool badmin) + private void WriteLogToDb(Character sender, string text) { var str_trunc = text; - var adminright = sender.AccessLevel; if (text.Length > 255) { From 92a2df03bcf56f3349c90ac960a6523c8bbd68b9 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 6 Sep 2023 20:21:16 +0300 Subject: [PATCH 395/457] CommandLog - minor script change --- .../Services/Channels/ChatCommands/GameAdminCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs index b4b1f1385..f3cb8bf53 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/GameAdminCommands.cs @@ -62,7 +62,7 @@ private void WriteLogToDb(Character sender, string text) str_trunc = text.Substring(1, 255); //text truncated to avoid big text spam insertion in db } - Db.Query().CommandText("insert adminCommandLog (characterid, acclevel, message) values (@characterid, @acclevel, @text)") + 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) From 0a9e795397fbd4281ada3bd6fbe3c9c44638b7ef Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 20:34:28 +1000 Subject: [PATCH 396/457] add compile workflow --- workflows/Compile.yml | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 workflows/Compile.yml diff --git a/workflows/Compile.yml b/workflows/Compile.yml new file mode 100644 index 000000000..672cb267b --- /dev/null +++ b/workflows/Compile.yml @@ -0,0 +1,58 @@ +name: .NET Core Desktop + +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] + configuration: [Debug] + + 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. + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v1.0.2 + + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: | + nuget restore + msbuild $env:Solution_Name -p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.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()}} \ No newline at end of file From cd2d0c26cb287a5611ee89ef97fe33a124e5b2f2 Mon Sep 17 00:00:00 2001 From: Urglefloggah666 Date: Mon, 18 Sep 2023 20:49:55 +1000 Subject: [PATCH 397/457] Create build.yml add build workflow/action --- .github/workflows/build.yml | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..cd21bb245 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,58 @@ +name: .NET Core Desktop + +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] + configuration: [Debug] + + 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. + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v1.0.2 + + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: | + nuget restore + msbuild $env:Solution_Name -p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.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()}} From a8b3e85d0fa43050d6a5833101e76de6727305f9 Mon Sep 17 00:00:00 2001 From: Urglefloggah Date: Mon, 18 Sep 2023 20:57:49 +1000 Subject: [PATCH 398/457] remove compile.yml --- workflows/Compile.yml | 58 ------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 workflows/Compile.yml diff --git a/workflows/Compile.yml b/workflows/Compile.yml deleted file mode 100644 index 672cb267b..000000000 --- a/workflows/Compile.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: .NET Core Desktop - -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] - configuration: [Debug] - - 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. - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - # Install the .NET Core workload - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.0.2 - - - # Restore the application to populate the obj folder with RuntimeIdentifiers - - name: Restore the application - run: | - nuget restore - msbuild $env:Solution_Name -p:Configuration=$env:Configuration - env: - Configuration: ${{ matrix.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()}} \ No newline at end of file From 1bded1331e294e1822041989e2c9254d0b14fe6d Mon Sep 17 00:00:00 2001 From: Line <34966518+Sellafield@users.noreply.github.com> Date: Wed, 4 Oct 2023 02:11:33 +0300 Subject: [PATCH 399/457] - Initial commit. Bandwidth hardcoded --- .../PerpetuumBootstrapper.cs | 5 + src/Perpetuum.ExportedTypes/AggregateField.cs | 6 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 10 ++ .../EntityFramework/EntityDefaultOptions.cs | 11 ++ src/Perpetuum/ErrorCodes.cs | 1 + src/Perpetuum/Keywords.cs | 3 + src/Perpetuum/Modules/TurretLauncherModule.cs | 96 +++++++++++ src/Perpetuum/Perpetuum.csproj | 5 + src/Perpetuum/Players/Player.cs | 3 + src/Perpetuum/Robots/Robot.Properties.cs | 12 ++ .../Robots/Robot.RemoteOperations.cs | 51 ++++++ src/Perpetuum/Robots/Robot.cs | 4 + src/Perpetuum/Units/Unit.Visibility.cs | 12 ++ src/Perpetuum/Units/Unit.cs | 9 + src/Perpetuum/Zones/NpcSystem/Npc.cs | 21 ++- .../Zones/SentryTurrets/BandwidthHandler.cs | 160 ++++++++++++++++++ .../Zones/SentryTurrets/RemoteChannel.cs | 39 +++++ .../Zones/SentryTurrets/SentryTurret.cs | 63 +++++++ 18 files changed, 508 insertions(+), 3 deletions(-) create mode 100644 src/Perpetuum/Modules/TurretLauncherModule.cs create mode 100644 src/Perpetuum/Robots/Robot.RemoteOperations.cs create mode 100644 src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs create mode 100644 src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs create mode 100644 src/Perpetuum/Zones/SentryTurrets/SentryTurret.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index a2cbf76c5..eb3778034 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -188,6 +188,7 @@ using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; using Perpetuum.Items.Helpers; using Perpetuum.Zones.LandMines; +using Perpetuum.Zones.SentryTurrets; namespace Perpetuum.Bootstrapper { @@ -965,6 +966,7 @@ private void RegisterEntities() RegisterModule(); RegisterModule(); RegisterModule(); + RegisterModule(); RegisterModule(); RegisterModule(); RegisterModule(); @@ -985,6 +987,7 @@ private void RegisterEntities() RegisterEntity(); RegisterUnit(); + RegisterUnit(); RegisterUnit(); RegisterUnit(); RegisterUnit(); @@ -1195,6 +1198,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_turret_launchers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_turret_launcher_ammo)); ByCategoryFlags(CategoryFlags.cf_webber); ByCategoryFlags(CategoryFlags.cf_sensor_dampeners); ByCategoryFlags(CategoryFlags.cf_remote_sensor_boosters); @@ -1240,6 +1244,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_robotshards); ByCategoryFlags(CategoryFlags.cf_others); ByCategoryFlags(CategoryFlags.cf_blob_emitter); + ByCategoryFlags(CategoryFlags.cf_sentry_turrets); ByCategoryFlags(CategoryFlags.cf_reactor_cores); ByCategoryFlags(CategoryFlags.cf_kiosk); ByCategoryFlags(CategoryFlags.cf_alarm_switch); diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 6f224399f..d921467d2 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -432,6 +432,8 @@ public enum AggregateField turret_cycle_time_modifier = 335, turret_fallof_modifier = 336, turret_optimal_range_modifier = 337, - weapon_cycle_time_modifier = 540 - } + weapon_cycle_time_modifier = 540, + //bandwidth_max = 672, + //bandwidth_max_modifier = 673, + } } diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 555b44290..4732099a0 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -50,6 +50,8 @@ public enum CategoryFlags : long 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, @@ -105,6 +107,12 @@ public enum CategoryFlags : long cf_construction_ammo_main = 0x000000000000110A, cf_construction_ammo = 0x000000000001110A, cf_wall_builder_ammo = 0x000000000002110A, + cf_turret_launcher_ammo = 0x000000000000200A, + cf_sentry_turret_ammo = 0x000000000010200A, + cf_em_sentry_turret_ammo = 0x000000001010200A, + cf_laser_sentry_turret_ammo = 0x000000002010200A, + cf_missile_sentry_turret_ammo = 0x000000003010200A, + cf_firearms_sentry_turret_ammo = 0x000000004010200A, cf_robot_equipment = 0x000000000000000F, cf_armor_equipment = 0x000000000000010F, @@ -255,6 +263,7 @@ public enum CategoryFlags : long cf_small_industrial_aoe = 0x00000000010A070F, cf_medium_industrial_aoe = 0x00000000020A070F, cf_large_industrial_aoe = 0x00000000030A070F, + cf_turret_launchers = 0x00000000000B070F, cf_terraforming_equipment = 0x000000000000080F, cf_small_terraformer = 0x000000000001080F, cf_terraforming_digger = 0x000000000002080F, @@ -550,6 +559,7 @@ public enum CategoryFlags : long cf_gate = 0x0000000000000A92, cf_rifts = 0x0000000000000B92, cf_portals = 0x0000000000000C92, + cf_sentry_turrets = 0x0000000000000D92, cf_production_items = 0x0000000000000094, cf_research_kits = 0x0000000000000194, diff --git a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs index ec220c34e..623a8c06b 100644 --- a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs +++ b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs @@ -174,5 +174,16 @@ public int SparkID return id; } } + + public int TurretId + { + get + { + var id = _dictionary.GetOrDefault(k.TurretId, 0); + Debug.Assert(id > 0); + + return id; + } + } } } \ No newline at end of file diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index 1580ced9e..aa3b4f6c4 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -711,5 +711,6 @@ public enum ErrorCodes MaxExpiringBasePerZoneReached, MaxExpiringBasePerZonePerCorpReached, RespecTokenIsNotReadyYet, + MaxBandwidthExceed, } } diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index 98fd73323..78035e4ec 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1220,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/TurretLauncherModule.cs b/src/Perpetuum/Modules/TurretLauncherModule.cs new file mode 100644 index 000000000..7ad942f04 --- /dev/null +++ b/src/Perpetuum/Modules/TurretLauncherModule.cs @@ -0,0 +1,96 @@ +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Players; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.Finders.PositionFinders; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones; +using System; +using System.Linq; +using Perpetuum.Zones.Finders; +using Perpetuum.Units; + +namespace Perpetuum.Modules +{ + public class TurretLauncherModule : ActiveModule + { + private const int SentryTurretHeight = 7; + private const double SentryTurretDeployRange = 2; + + public TurretLauncherModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) + { + optimalRange.AddEffectModifier(AggregateField.effect_ew_optimal_range_modifier); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + protected override void OnAction() + { + var zone = Zone; + + if (zone == null) + { + return; + } + + Position? lockPosition; + + var myLock = GetLock(); + + if (myLock is TerrainLock) + { + lockPosition = (myLock as TerrainLock).Location.AddToZ(SentryTurretHeight); + } + else if (myLock is UnitLock) + { + lockPosition = (myLock as UnitLock).Target.CurrentPosition.AddToZ(SentryTurretHeight); + } + else + { + OnError(ErrorCodes.InvalidLockType); + + return; + } + + Position targetPosition = lockPosition.Value; + zone.Units.OfType().WithinRange(targetPosition, SentryTurretDeployRange).Any().ThrowIfTrue(ErrorCodes.BlobEmitterInRange); + + var r = zone.IsInLineOfSight(ParentRobot, targetPosition, false); + + if (r.hit) + { + OnError(ErrorCodes.LOSFailed); + + return; + } + + var ammo = GetAmmo(); + var fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + + ParentRobot.HasFreeBandwidthOf(fieldTurret).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); + ParentRobot.UseRemoteChannel(fieldTurret); + + var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); + + fieldTurret.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); + + var finder = new ClosestWalkablePositionFinder(zone, targetPosition); + var position = finder.FindOrThrow(); + var beamBuilder = Beam.NewBuilder() + .WithType(BeamType.deploy_device_01) + .WithPosition(targetPosition) + .WithDuration(TimeSpan.FromSeconds(5)); + + fieldTurret.AddToZone(zone, position, ZoneEnterType.Default, beamBuilder); + + ConsumeAmmo(); + } + } +} diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 5aeb0ad45..e2b475b50 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -261,6 +261,7 @@ + @@ -287,6 +288,7 @@ + @@ -652,6 +654,9 @@ + + + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 40dd7f62b..ab7ad436b 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -45,6 +45,7 @@ using Perpetuum.Zones.PBS; using Perpetuum.Zones.PlantTools; using Perpetuum.Zones.ProximityProbes; +using Perpetuum.Zones.SentryTurrets; using Perpetuum.Zones.Teleporting; using Perpetuum.Zones.Teleporting.Strategies; using Perpetuum.Zones.Terrains; @@ -66,7 +67,9 @@ public void Update(TimeSpan elapsed) { var speed = _player.Speed; if (speed <= 0.0) + { return; + } elapsed = elapsed.Min(_maxElapsedTime); diff --git a/src/Perpetuum/Robots/Robot.Properties.cs b/src/Perpetuum/Robots/Robot.Properties.cs index c6eb91f31..c3e3d00b5 100644 --- a/src/Perpetuum/Robots/Robot.Properties.cs +++ b/src/Perpetuum/Robots/Robot.Properties.cs @@ -22,6 +22,7 @@ partial class Robot private ItemProperty missileHitChance; private ItemProperty decayChance; private ItemProperty mineDetectionRange; + //private ItemProperty bandwidthMax; private void InitProperties() { @@ -58,6 +59,12 @@ private void InitProperties() AggregateField.undefined, AggregateField.effect_mine_detection_range_modifier); AddProperty(mineDetectionRange); + + //bandwidthMax = new UnitProperty( + // this, + // AggregateField.bandwidth_max, + // AggregateField.bandwidth_max_modifier); + //AddProperty(bandwidthMax); } private double PowerGridMax @@ -107,6 +114,11 @@ public double MineDetectionRange get { return mineDetectionRange.Value; } } + //public double Bandwidth + //{ + // get { return 15; /*bandwidthMax.Value;*/ } + //} + public override void UpdateRelatedProperties(AggregateField field) { foreach (var component in RobotComponents) diff --git a/src/Perpetuum/Robots/Robot.RemoteOperations.cs b/src/Perpetuum/Robots/Robot.RemoteOperations.cs new file mode 100644 index 000000000..2f7c87891 --- /dev/null +++ b/src/Perpetuum/Robots/Robot.RemoteOperations.cs @@ -0,0 +1,51 @@ +using Perpetuum.Units; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking; +using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perpetuum.Zones.SentryTurrets; + +namespace Perpetuum.Robots +{ + partial class Robot + { + private BandwidthHandler bandwidthHandler; + + private void InitBandwidthHandler() + { + bandwidthHandler = new BandwidthHandler(this); + } + + [CanBeNull] + public RemoteChannel GetRemoteChannel(long channelId) + { + return bandwidthHandler.GetRemoteChannel(channelId); + } + + [CanBeNull] + public RemoteChannel GetRemoteChannelByUnit(Unit unit) + { + return bandwidthHandler.GetRemoteChannelByUnit(unit); + } + + public bool HasFreeBandwidthOf(SentryTurret turret) + { + return bandwidthHandler.HasFreeBandwidthOf(turret); + } + + public void UseRemoteChannel(SentryTurret turret) + { + bandwidthHandler.UseRemoteChannel(turret); + turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; + } + + public void UseRemoteChannel(RemoteChannel newChannel) + { + bandwidthHandler.UseRemoteChannel(newChannel); + } + } +} diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 819eeb2c4..a0f76c910 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -19,6 +19,7 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.SentryTurrets; namespace Perpetuum.Robots { @@ -60,6 +61,7 @@ public abstract partial class Robot : Unit protected Robot() { InitLockHander(); + InitBandwidthHandler(); InitProperties(); } @@ -78,6 +80,7 @@ public override void Initialize() private Lazy> _activeModules; private Lazy> _components; private Lazy> _robotComponents; + private void InitComponents() { _components = new Lazy>(() => Children.OfType().ToArray()); @@ -167,6 +170,7 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); _lockHandler.Update(time); + bandwidthHandler.Update(); foreach (var robotComponent in RobotComponents) { diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index e49557b2b..7e65d9cb0 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -7,7 +7,9 @@ using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; +using Perpetuum.Zones.SentryTurrets; using Perpetuum.Zones.LandMines; +using Perpetuum.Zones.NpcSystem; namespace Perpetuum.Units { @@ -47,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); diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index e59ed4393..f941b54d6 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -24,11 +24,13 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Effects; using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.SentryTurrets; using Perpetuum.Zones.Gates; using Perpetuum.Zones.Locking; using Perpetuum.Zones.PBS; using Perpetuum.Zones.PBS.DockingBases; using Perpetuum.Zones.PBS.Turrets; +using Perpetuum.Zones.NpcSystem; namespace Perpetuum.Units { @@ -919,10 +921,17 @@ protected bool IsHostile(Unit unit) } 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; } + internal virtual bool IsHostile(SentryTurret turret) { return false; } + + internal virtual bool IsHostile(Npc npc) { return false; } + public void StopMoving() { CurrentSpeed = 0; diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index d98f913e0..4f80eca6a 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -23,6 +23,7 @@ using Perpetuum.Units; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.SentryTurrets; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Movements; @@ -982,7 +983,7 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) if (EP > 0) { var awardedPlayers = new List(); - foreach (var hostile in ThreatManager.Hostiles) + foreach (var hostile in ThreatManager.Hostiles.Where(x=>x.unit is Player)) { var playerUnit = hostile.unit; var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); @@ -1179,6 +1180,16 @@ internal override bool IsHostile(AreaBomb bomb) return true; } + internal override bool IsHostile(SentryTurret turret) + { + return true; + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + /// /// This determines if threat can be added to a target based on the following: /// - Is the target already on the threat manager @@ -1198,6 +1209,14 @@ public bool CanAddThreatTo(Unit target, Threat threat) return threat.type != ThreatType.Undefined; } + protected override void UpdateUnitVisibility(Unit target) + { + if (target is SentryTurret) + { + UpdateVisibility(target); + } + } + private void AddBodyPullThreat(Unit enemy) { if ( !IsHostile(enemy)) diff --git a/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs b/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs new file mode 100644 index 000000000..c2d1a580d --- /dev/null +++ b/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs @@ -0,0 +1,160 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Robots; +using Perpetuum.Units; +using Perpetuum.Zones.Blobs; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.SentryTurrets +{ + public class BandwidthHandler + { + private readonly Robot owner; + //private readonly ItemProperty bandwidth; + private readonly int bandwidth; + private List channels = new List(); + private readonly ConcurrentQueue newChannels = new ConcurrentQueue(); + private readonly ConcurrentQueue deactivatedChannels = new ConcurrentQueue(); + private int dirty; + + public BandwidthHandler(Robot owner) + { + this.owner = owner; + + bandwidth = 15; + + //bandwidth = new BandwidthMaxProperty(owner); + //owner.AddProperty(bandwidth); + } + + //private int Bandwidth { get { return (int)bandwidth.Value; } } + + private int Bandwidth { get { return bandwidth; } } + + public int BandwidthUsed + { + get + { + return channels.Any() + ? channels.Sum(x => x.Turret?.BandwidthUsage ?? 0) + : 0; + } + } + + public List Turrets + { + get { return channels; } + } + + public bool HasFreeBandwidthOf(SentryTurret turret) + { + return BandwidthUsed <= Bandwidth - turret.BandwidthUsage; + } + + public void UseRemoteChannel(SentryTurret turret) + { + UseRemoteChannel(new RemoteChannel(owner, turret)); + } + + public void UseRemoteChannel(RemoteChannel newChannel) + { + newChannels.Enqueue(newChannel); + Interlocked.Exchange(ref dirty, 1); + } + + public void OnRemoteChannelDeactivated(SentryTurret turret) + { + var channel = GetRemoteChannelByUnit(turret); + if (channel != null) + { + ReleaseRemoteChannel(channel); + turret.RemoteChannelDeactivated -= OnRemoteChannelDeactivated; + } + } + + 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); + } + + [CanBeNull] + private RemoteChannel GetRemoteChannelByEid(long unitEid) + { + return channels.OfType().FirstOrDefault(l => l.Turret.Eid == unitEid); + } + + //private class BandwidthMaxProperty : UnitProperty + //{ + // public BandwidthMaxProperty(Unit owner) : base(owner, AggregateField.bandwidth_max, AggregateField.bandwidth_max_modifier) + // { + // } + //} + } +} diff --git a/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs b/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs new file mode 100644 index 000000000..88544677f --- /dev/null +++ b/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs @@ -0,0 +1,39 @@ +using Perpetuum.Robots; +using Perpetuum.Units; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perpetuum.Timers; + +namespace Perpetuum.Zones.SentryTurrets +{ + public delegate void RemoteChannelEventHandler(SentryTurret turret); + public delegate void RemoteChannelEventHandler(RemoteChannel channel, T arg); + + public class RemoteChannel + { + public long Id { get; private set; } + + public RemoteChannel(Robot owner, SentryTurret turret) + { + Id = FastRandom.NextLong(); + Owner = owner; + Turret = turret; + } + + public Robot Owner { get; private set; } + + public SentryTurret Turret { get; set; } + + 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/SentryTurrets/SentryTurret.cs b/src/Perpetuum/Zones/SentryTurrets/SentryTurret.cs new file mode 100644 index 000000000..906895b52 --- /dev/null +++ b/src/Perpetuum/Zones/SentryTurrets/SentryTurret.cs @@ -0,0 +1,63 @@ +using Perpetuum.Players; +using Perpetuum.Robots; +using Perpetuum.Units; +using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.LandMines; +using Perpetuum.Zones.NpcSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.SentryTurrets +{ + public class SentryTurret : Turret + { + private UnitDespawnHelper _despawnHelper; + + public event RemoteChannelEventHandler RemoteChannelDeactivated; + + public int BandwidthUsage { get; private set; } = 3; + + public TimeSpan DespawnTime + { + set { _despawnHelper = UnitDespawnHelper.Create(this, value); } + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + _despawnHelper?.Update(time, this); + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + internal override bool IsHostile(Player player) + { + return false; + } + + internal override bool IsHostile(Npc npc) + { + return true; + } + + protected override void UpdateUnitVisibility(Unit target) + { + if (target is Npc) + { + UpdateVisibility(target); + } + } + + protected override void OnRemovedFromZone(IZone zone) + { + RemoteChannelDeactivated(this); + base.OnRemovedFromZone(zone); + } + } +} From 451147ecec87b9b8ca23def30ff3a5457a7d912f Mon Sep 17 00:00:00 2001 From: Line <34966518+Sellafield@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:52:22 +0300 Subject: [PATCH 400/457] - Working limits. Migth worth to revisit logic --- src/Perpetuum.ExportedTypes/AggregateField.cs | 3 +- src/Perpetuum/Modules/TurretLauncherModule.cs | 15 +++++-- src/Perpetuum/Perpetuum.csproj | 2 +- src/Perpetuum/Robots/Robot.Properties.cs | 12 ------ src/Perpetuum/Robots/Robot.cs | 2 - .../Zones/SentryTurrets/BandwidthHandler.cs | 25 ++---------- .../Zones/SentryTurrets/RemoteChannel.cs | 5 ++- .../SentryTurrets/RemoteController.cs} | 40 ++++++++++++++----- 8 files changed, 52 insertions(+), 52 deletions(-) rename src/Perpetuum/{Robots/Robot.RemoteOperations.cs => Zones/SentryTurrets/RemoteController.cs} (53%) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index d921467d2..e8194b75f 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -433,7 +433,6 @@ public enum AggregateField turret_fallof_modifier = 336, turret_optimal_range_modifier = 337, weapon_cycle_time_modifier = 540, - //bandwidth_max = 672, - //bandwidth_max_modifier = 673, + bandwidth_max = 672, } } diff --git a/src/Perpetuum/Modules/TurretLauncherModule.cs b/src/Perpetuum/Modules/TurretLauncherModule.cs index 7ad942f04..0fb113c23 100644 --- a/src/Perpetuum/Modules/TurretLauncherModule.cs +++ b/src/Perpetuum/Modules/TurretLauncherModule.cs @@ -17,10 +17,12 @@ public class TurretLauncherModule : ActiveModule { private const int SentryTurretHeight = 7; private const double SentryTurretDeployRange = 2; - + public RemoteController RemoteController { get; private set; } + public TurretLauncherModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) { optimalRange.AddEffectModifier(AggregateField.effect_ew_optimal_range_modifier); + RemoteController = new RemoteController(this); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -33,6 +35,13 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override void OnAction() { + if (RemoteController == null) + { + return; + } + + RemoteController.SyncRemoteChannels(); + var zone = Zone; if (zone == null) @@ -74,8 +83,8 @@ protected override void OnAction() var ammo = GetAmmo(); var fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); - ParentRobot.HasFreeBandwidthOf(fieldTurret).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); - ParentRobot.UseRemoteChannel(fieldTurret); + RemoteController.HasFreeBandwidthOf(fieldTurret).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); + RemoteController.UseRemoteChannel(fieldTurret); var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index e2b475b50..d52bb9952 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -288,7 +288,6 @@ - @@ -656,6 +655,7 @@ + diff --git a/src/Perpetuum/Robots/Robot.Properties.cs b/src/Perpetuum/Robots/Robot.Properties.cs index c3e3d00b5..c6eb91f31 100644 --- a/src/Perpetuum/Robots/Robot.Properties.cs +++ b/src/Perpetuum/Robots/Robot.Properties.cs @@ -22,7 +22,6 @@ partial class Robot private ItemProperty missileHitChance; private ItemProperty decayChance; private ItemProperty mineDetectionRange; - //private ItemProperty bandwidthMax; private void InitProperties() { @@ -59,12 +58,6 @@ private void InitProperties() AggregateField.undefined, AggregateField.effect_mine_detection_range_modifier); AddProperty(mineDetectionRange); - - //bandwidthMax = new UnitProperty( - // this, - // AggregateField.bandwidth_max, - // AggregateField.bandwidth_max_modifier); - //AddProperty(bandwidthMax); } private double PowerGridMax @@ -114,11 +107,6 @@ public double MineDetectionRange get { return mineDetectionRange.Value; } } - //public double Bandwidth - //{ - // get { return 15; /*bandwidthMax.Value;*/ } - //} - public override void UpdateRelatedProperties(AggregateField field) { foreach (var component in RobotComponents) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index a0f76c910..674361e7a 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -61,7 +61,6 @@ public abstract partial class Robot : Unit protected Robot() { InitLockHander(); - InitBandwidthHandler(); InitProperties(); } @@ -170,7 +169,6 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); _lockHandler.Update(time); - bandwidthHandler.Update(); foreach (var robotComponent in RobotComponents) { diff --git a/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs b/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs index c2d1a580d..63e9a092c 100644 --- a/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs @@ -13,33 +13,23 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Perpetuum.Modules; namespace Perpetuum.Zones.SentryTurrets { public class BandwidthHandler { - private readonly Robot owner; - //private readonly ItemProperty bandwidth; - private readonly int bandwidth; + private readonly TurretLauncherModule owner; private List channels = new List(); private readonly ConcurrentQueue newChannels = new ConcurrentQueue(); private readonly ConcurrentQueue deactivatedChannels = new ConcurrentQueue(); private int dirty; - public BandwidthHandler(Robot owner) + public BandwidthHandler(TurretLauncherModule owner) { this.owner = owner; - - bandwidth = 15; - - //bandwidth = new BandwidthMaxProperty(owner); - //owner.AddProperty(bandwidth); } - //private int Bandwidth { get { return (int)bandwidth.Value; } } - - private int Bandwidth { get { return bandwidth; } } - public int BandwidthUsed { get @@ -57,7 +47,7 @@ public List Turrets public bool HasFreeBandwidthOf(SentryTurret turret) { - return BandwidthUsed <= Bandwidth - turret.BandwidthUsage; + return BandwidthUsed <= owner.RemoteController.BandwidthMax - turret.BandwidthUsage; } public void UseRemoteChannel(SentryTurret turret) @@ -149,12 +139,5 @@ private RemoteChannel GetRemoteChannelByEid(long unitEid) { return channels.OfType().FirstOrDefault(l => l.Turret.Eid == unitEid); } - - //private class BandwidthMaxProperty : UnitProperty - //{ - // public BandwidthMaxProperty(Unit owner) : base(owner, AggregateField.bandwidth_max, AggregateField.bandwidth_max_modifier) - // { - // } - //} } } diff --git a/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs b/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs index 88544677f..5f45800fc 100644 --- a/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs +++ b/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading.Tasks; using Perpetuum.Timers; +using Perpetuum.Modules; namespace Perpetuum.Zones.SentryTurrets { @@ -18,14 +19,14 @@ public class RemoteChannel { public long Id { get; private set; } - public RemoteChannel(Robot owner, SentryTurret turret) + public RemoteChannel(TurretLauncherModule owner, SentryTurret turret) { Id = FastRandom.NextLong(); Owner = owner; Turret = turret; } - public Robot Owner { get; private set; } + public TurretLauncherModule Owner { get; private set; } public SentryTurret Turret { get; set; } diff --git a/src/Perpetuum/Robots/Robot.RemoteOperations.cs b/src/Perpetuum/Zones/SentryTurrets/RemoteController.cs similarity index 53% rename from src/Perpetuum/Robots/Robot.RemoteOperations.cs rename to src/Perpetuum/Zones/SentryTurrets/RemoteController.cs index 2f7c87891..67873a1e6 100644 --- a/src/Perpetuum/Robots/Robot.RemoteOperations.cs +++ b/src/Perpetuum/Zones/SentryTurrets/RemoteController.cs @@ -1,23 +1,45 @@ -using Perpetuum.Units; -using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.Locking; -using Perpetuum.Zones; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules; +using Perpetuum.Robots; +using Perpetuum.Units; +using Perpetuum.Zones.Blobs.BlobEmitters; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Perpetuum.Zones.SentryTurrets; -namespace Perpetuum.Robots +namespace Perpetuum.Zones.SentryTurrets { - partial class Robot + public class RemoteController { + private readonly ItemProperty bandwidthMax; private BandwidthHandler bandwidthHandler; - private void InitBandwidthHandler() + public RemoteController(TurretLauncherModule module) { - bandwidthHandler = new BandwidthHandler(this); + bandwidthMax = new ModuleProperty( + module, + AggregateField.bandwidth_max); + module.AddProperty(bandwidthMax); + + InitBandwidthHandler(module); + } + + public double BandwidthMax + { + get { return bandwidthMax.Value; } + } + + private void InitBandwidthHandler(TurretLauncherModule module) + { + bandwidthHandler = new BandwidthHandler(module); + } + + public void SyncRemoteChannels() + { + this.bandwidthHandler.Update(); } [CanBeNull] From d8cb2f26512fc5ee540101124fa7b718665f6ceb Mon Sep 17 00:00:00 2001 From: Line <34966518+Sellafield@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:00:36 +0300 Subject: [PATCH 401/457] - Working limits with extension --- .../PerpetuumBootstrapper.cs | 8 +- src/Perpetuum.ExportedTypes/AggregateField.cs | 4 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 10 +-- src/Perpetuum/Keywords.cs | 2 +- ...tLauncherModule.cs => RemoteController.cs} | 71 +++++++++++++++--- src/Perpetuum/Perpetuum.csproj | 10 +-- src/Perpetuum/Players/Player.cs | 2 +- src/Perpetuum/Robots/Robot.cs | 2 +- src/Perpetuum/Units/Unit.Visibility.cs | 2 +- src/Perpetuum/Units/Unit.cs | 2 +- src/Perpetuum/Zones/NpcSystem/Npc.cs | 2 +- .../BandwidthHandler.cs | 14 ++-- .../RemoteChannel.cs | 6 +- .../RemoteControl/RemoteControlledUnit.cs | 24 ++++++ .../SentryTurret.cs | 22 +++++- .../Zones/SentryTurrets/RemoteController.cs | 73 ------------------- 16 files changed, 134 insertions(+), 120 deletions(-) rename src/Perpetuum/Modules/{TurretLauncherModule.cs => RemoteController.cs} (56%) rename src/Perpetuum/Zones/{SentryTurrets => RemoteControl}/BandwidthHandler.cs (89%) rename src/Perpetuum/Zones/{SentryTurrets => RemoteControl}/RemoteChannel.cs (83%) create mode 100644 src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs rename src/Perpetuum/Zones/{SentryTurrets => RemoteControl}/SentryTurret.cs (70%) delete mode 100644 src/Perpetuum/Zones/SentryTurrets/RemoteController.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index eb3778034..107b8dcfc 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -188,7 +188,7 @@ using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; using Perpetuum.Items.Helpers; using Perpetuum.Zones.LandMines; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; namespace Perpetuum.Bootstrapper { @@ -928,6 +928,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -966,7 +967,7 @@ private void RegisterEntities() RegisterModule(); RegisterModule(); RegisterModule(); - RegisterModule(); + RegisterModule(); RegisterModule(); RegisterModule(); RegisterModule(); @@ -1112,6 +1113,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_probe_ammo_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_one_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_artifact); @@ -1198,7 +1200,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_turret_launchers, new NamedParameter("ammoCategoryFlags", CategoryFlags.cf_turret_launcher_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); diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index e8194b75f..0e0b1e44c 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -433,6 +433,8 @@ public enum AggregateField turret_fallof_modifier = 336, turret_optimal_range_modifier = 337, weapon_cycle_time_modifier = 540, - bandwidth_max = 672, + remote_control_bandwidth_max = 672, + remote_control_bandwidth_max_modifier = 673, + remote_control_bandwidth_usage = 674, } } diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 4732099a0..254b32b00 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -107,12 +107,8 @@ public enum CategoryFlags : long cf_construction_ammo_main = 0x000000000000110A, cf_construction_ammo = 0x000000000001110A, cf_wall_builder_ammo = 0x000000000002110A, - cf_turret_launcher_ammo = 0x000000000000200A, - cf_sentry_turret_ammo = 0x000000000010200A, - cf_em_sentry_turret_ammo = 0x000000001010200A, - cf_laser_sentry_turret_ammo = 0x000000002010200A, - cf_missile_sentry_turret_ammo = 0x000000003010200A, - cf_firearms_sentry_turret_ammo = 0x000000004010200A, + cf_remote_controlled_units = 0x000000000000200A, + cf_sentry_turret_units = 0x000000000010200A, cf_robot_equipment = 0x000000000000000F, cf_armor_equipment = 0x000000000000010F, @@ -263,7 +259,7 @@ public enum CategoryFlags : long cf_small_industrial_aoe = 0x00000000010A070F, cf_medium_industrial_aoe = 0x00000000020A070F, cf_large_industrial_aoe = 0x00000000030A070F, - cf_turret_launchers = 0x00000000000B070F, + cf_remote_controllers = 0x00000000000B070F, cf_terraforming_equipment = 0x000000000000080F, cf_small_terraformer = 0x000000000001080F, cf_terraforming_digger = 0x000000000002080F, diff --git a/src/Perpetuum/Keywords.cs b/src/Perpetuum/Keywords.cs index 78035e4ec..d02553719 100644 --- a/src/Perpetuum/Keywords.cs +++ b/src/Perpetuum/Keywords.cs @@ -1221,7 +1221,7 @@ public class k public const string es_admin_trashcan = "admin_trashcan"; public const string es_account_bound = "account_bound"; - //Definition of field turret + // Definition of field turret public const string TurretId = "turretId"; } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/TurretLauncherModule.cs b/src/Perpetuum/Modules/RemoteController.cs similarity index 56% rename from src/Perpetuum/Modules/TurretLauncherModule.cs rename to src/Perpetuum/Modules/RemoteController.cs index 0fb113c23..ef5f4b4b3 100644 --- a/src/Perpetuum/Modules/TurretLauncherModule.cs +++ b/src/Perpetuum/Modules/RemoteController.cs @@ -1,10 +1,9 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Players; using Perpetuum.Zones.Beams; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones; using System; using System.Linq; @@ -13,16 +12,64 @@ namespace Perpetuum.Modules { - public class TurretLauncherModule : ActiveModule + public class RemoteControllerModule : ActiveModule { private const int SentryTurretHeight = 7; - private const double SentryTurretDeployRange = 2; - public RemoteController RemoteController { get; private set; } + private const double SentryTurretDeployRange = 5; + private readonly ModuleProperty bandwidthMax; + private BandwidthHandler bandwidthHandler; - public TurretLauncherModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) + public double BandwidthMax + { + get { return bandwidthMax.Value; } + } + + public RemoteControllerModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) { optimalRange.AddEffectModifier(AggregateField.effect_ew_optimal_range_modifier); - RemoteController = new RemoteController(this); + + bandwidthMax = new ModuleProperty(this, AggregateField.remote_control_bandwidth_max); + this.AddProperty(bandwidthMax); + + InitBandwidthHandler(this); + } + + private void InitBandwidthHandler(RemoteControllerModule module) + { + bandwidthHandler = new BandwidthHandler(module); + } + + public void SyncRemoteChannels() + { + this.bandwidthHandler.Update(); + } + + [CanBeNull] + public RemoteChannel GetRemoteChannel(long channelId) + { + return bandwidthHandler.GetRemoteChannel(channelId); + } + + [CanBeNull] + public RemoteChannel GetRemoteChannelByUnit(Unit unit) + { + return bandwidthHandler.GetRemoteChannelByUnit(unit); + } + + public bool HasFreeBandwidthFor(RemoteControlledUnit unit) + { + return bandwidthHandler.HasFreeBandwidthFor(unit); + } + + public void UseRemoteChannel(SentryTurret turret) + { + bandwidthHandler.UseRemoteChannel(turret); + turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; + } + + public void UseRemoteChannel(RemoteChannel newChannel) + { + bandwidthHandler.UseRemoteChannel(newChannel); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -35,12 +82,12 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override void OnAction() { - if (RemoteController == null) + if (bandwidthHandler == null) { return; } - RemoteController.SyncRemoteChannels(); + SyncRemoteChannels(); var zone = Zone; @@ -80,11 +127,11 @@ protected override void OnAction() return; } - var ammo = GetAmmo(); + var ammo = GetAmmo() as RemoteControlledUnit; var fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); - RemoteController.HasFreeBandwidthOf(fieldTurret).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); - RemoteController.UseRemoteChannel(fieldTurret); + HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); + UseRemoteChannel(fieldTurret); var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d52bb9952..417f79cd6 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -261,7 +261,7 @@ - + @@ -653,10 +653,9 @@ - - - - + + + @@ -959,6 +958,7 @@ + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index ab7ad436b..1512bd003 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -45,7 +45,7 @@ using Perpetuum.Zones.PBS; using Perpetuum.Zones.PlantTools; using Perpetuum.Zones.ProximityProbes; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Teleporting; using Perpetuum.Zones.Teleporting.Strategies; using Perpetuum.Zones.Terrains; diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 674361e7a..ff91983da 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -19,7 +19,7 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; namespace Perpetuum.Robots { diff --git a/src/Perpetuum/Units/Unit.Visibility.cs b/src/Perpetuum/Units/Unit.Visibility.cs index 7e65d9cb0..22dd5cc21 100644 --- a/src/Perpetuum/Units/Unit.Visibility.cs +++ b/src/Perpetuum/Units/Unit.Visibility.cs @@ -7,7 +7,7 @@ using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.LandMines; using Perpetuum.Zones.NpcSystem; diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index f941b54d6..9e293a445 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -24,7 +24,7 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Effects; using Perpetuum.Zones.Eggs; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Gates; using Perpetuum.Zones.Locking; using Perpetuum.Zones.PBS; diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 4f80eca6a..59ace8a2c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -23,7 +23,7 @@ using Perpetuum.Units; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Eggs; -using Perpetuum.Zones.SentryTurrets; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Movements; diff --git a/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs similarity index 89% rename from src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs rename to src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index 63e9a092c..40bf924e8 100644 --- a/src/Perpetuum/Zones/SentryTurrets/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -15,27 +15,27 @@ using System.Threading.Tasks; using Perpetuum.Modules; -namespace Perpetuum.Zones.SentryTurrets +namespace Perpetuum.Zones.RemoteControl { public class BandwidthHandler { - private readonly TurretLauncherModule owner; + 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 BandwidthHandler(TurretLauncherModule owner) + public BandwidthHandler(RemoteControllerModule owner) { this.owner = owner; } - public int BandwidthUsed + public double BandwidthUsed { get { return channels.Any() - ? channels.Sum(x => x.Turret?.BandwidthUsage ?? 0) + ? channels.Sum(x => x.Turret?.RemoteChannelBandwidthUsage ?? 0) : 0; } } @@ -45,9 +45,9 @@ public List Turrets get { return channels; } } - public bool HasFreeBandwidthOf(SentryTurret turret) + public bool HasFreeBandwidthFor(RemoteControlledUnit unit) { - return BandwidthUsed <= owner.RemoteController.BandwidthMax - turret.BandwidthUsage; + return BandwidthUsed <= owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; } public void UseRemoteChannel(SentryTurret turret) diff --git a/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs similarity index 83% rename from src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs rename to src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs index 5f45800fc..2b2a1629c 100644 --- a/src/Perpetuum/Zones/SentryTurrets/RemoteChannel.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs @@ -10,7 +10,7 @@ using Perpetuum.Timers; using Perpetuum.Modules; -namespace Perpetuum.Zones.SentryTurrets +namespace Perpetuum.Zones.RemoteControl { public delegate void RemoteChannelEventHandler(SentryTurret turret); public delegate void RemoteChannelEventHandler(RemoteChannel channel, T arg); @@ -19,14 +19,14 @@ public class RemoteChannel { public long Id { get; private set; } - public RemoteChannel(TurretLauncherModule owner, SentryTurret turret) + public RemoteChannel(RemoteControllerModule owner, SentryTurret turret) { Id = FastRandom.NextLong(); Owner = owner; Turret = turret; } - public TurretLauncherModule Owner { get; private set; } + public RemoteControllerModule Owner { get; private set; } public SentryTurret Turret { get; set; } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs new file mode 100644 index 000000000..a32ee4b87 --- /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 rcBandwidthUsage = ItemProperty.None; + + public override void Initialize() + { + rcBandwidthUsage = new AmmoProperty(this, AggregateField.remote_control_bandwidth_usage); + AddProperty(rcBandwidthUsage); + + base.Initialize(); + } + + public double RemoteChannelBandwidthUsage + { + get { return rcBandwidthUsage.Value; } + } + } +} diff --git a/src/Perpetuum/Zones/SentryTurrets/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs similarity index 70% rename from src/Perpetuum/Zones/SentryTurrets/SentryTurret.cs rename to src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 906895b52..b99ef4631 100644 --- a/src/Perpetuum/Zones/SentryTurrets/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -1,4 +1,7 @@ -using Perpetuum.Players; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Items.Ammos; +using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Units; using Perpetuum.Zones.Eggs; @@ -10,7 +13,7 @@ using System.Text; using System.Threading.Tasks; -namespace Perpetuum.Zones.SentryTurrets +namespace Perpetuum.Zones.RemoteControl { public class SentryTurret : Turret { @@ -18,13 +21,26 @@ public class SentryTurret : Turret public event RemoteChannelEventHandler RemoteChannelDeactivated; - public int BandwidthUsage { get; private set; } = 3; + private ItemProperty rcBandwidthUsage = ItemProperty.None; + + public double RemoteChannelBandwidthUsage + { + get { return rcBandwidthUsage.Value; } + } public TimeSpan DespawnTime { set { _despawnHelper = UnitDespawnHelper.Create(this, value); } } + public override void Initialize() + { + rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); + AddProperty(rcBandwidthUsage); + + base.Initialize(); + } + protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); diff --git a/src/Perpetuum/Zones/SentryTurrets/RemoteController.cs b/src/Perpetuum/Zones/SentryTurrets/RemoteController.cs deleted file mode 100644 index 67873a1e6..000000000 --- a/src/Perpetuum/Zones/SentryTurrets/RemoteController.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Perpetuum.ExportedTypes; -using Perpetuum.Items; -using Perpetuum.Modules; -using Perpetuum.Robots; -using Perpetuum.Units; -using Perpetuum.Zones.Blobs.BlobEmitters; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Perpetuum.Zones.SentryTurrets -{ - public class RemoteController - { - private readonly ItemProperty bandwidthMax; - private BandwidthHandler bandwidthHandler; - - public RemoteController(TurretLauncherModule module) - { - bandwidthMax = new ModuleProperty( - module, - AggregateField.bandwidth_max); - module.AddProperty(bandwidthMax); - - InitBandwidthHandler(module); - } - - public double BandwidthMax - { - get { return bandwidthMax.Value; } - } - - private void InitBandwidthHandler(TurretLauncherModule module) - { - bandwidthHandler = new BandwidthHandler(module); - } - - public void SyncRemoteChannels() - { - this.bandwidthHandler.Update(); - } - - [CanBeNull] - public RemoteChannel GetRemoteChannel(long channelId) - { - return bandwidthHandler.GetRemoteChannel(channelId); - } - - [CanBeNull] - public RemoteChannel GetRemoteChannelByUnit(Unit unit) - { - return bandwidthHandler.GetRemoteChannelByUnit(unit); - } - - public bool HasFreeBandwidthOf(SentryTurret turret) - { - return bandwidthHandler.HasFreeBandwidthOf(turret); - } - - public void UseRemoteChannel(SentryTurret turret) - { - bandwidthHandler.UseRemoteChannel(turret); - turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; - } - - public void UseRemoteChannel(RemoteChannel newChannel) - { - bandwidthHandler.UseRemoteChannel(newChannel); - } - } -} From d24cf1e30dc903464e96e63a506f6f68e5575d95 Mon Sep 17 00:00:00 2001 From: Line <34966518+Sellafield@users.noreply.github.com> Date: Sat, 7 Oct 2023 03:44:16 +0300 Subject: [PATCH 402/457] - Working extension for turrets damage --- src/Perpetuum.ExportedTypes/AggregateField.cs | 2 ++ src/Perpetuum/Modules/RemoteController.cs | 3 +++ src/Perpetuum/Modules/Weapons/WeaponAmmo.cs | 5 ++++- src/Perpetuum/Modules/Weapons/WeaponModule.cs | 5 +++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index 0e0b1e44c..f53a12281 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -436,5 +436,7 @@ public enum AggregateField remote_control_bandwidth_max = 672, remote_control_bandwidth_max_modifier = 673, remote_control_bandwidth_usage = 674, + remote_control_damage_modifier = 675, + remote_control_damage_modifier_modifier = 676, } } diff --git a/src/Perpetuum/Modules/RemoteController.cs b/src/Perpetuum/Modules/RemoteController.cs index ef5f4b4b3..1dc43be71 100644 --- a/src/Perpetuum/Modules/RemoteController.cs +++ b/src/Perpetuum/Modules/RemoteController.cs @@ -31,6 +31,9 @@ public RemoteControllerModule(CategoryFlags ammoCategoryFlags) : base(ammoCatego bandwidthMax = new ModuleProperty(this, AggregateField.remote_control_bandwidth_max); this.AddProperty(bandwidthMax); + var remoteControlDamageModifier = new ModuleProperty(this, AggregateField.remote_control_damage_modifier); + this.AddProperty(remoteControlDamageModifier); + InitBandwidthHandler(this); } diff --git a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs index a959a65b3..b09810464 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 { @@ -53,7 +54,9 @@ private IList CalculateCleanDamages() } } - var damageModifier = weapon.DamageModifier.ToPropertyModifier(); + var damageModifier = weapon.ParentRobot is SentryTurret + ? weapon.RemoteControlDamageModifier.ToPropertyModifier() + : weapon.DamageModifier.ToPropertyModifier(); var property = GetPropertyModifier(AggregateField.damage_chemical); diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 59faa97d1..f821e96e9 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -18,6 +18,9 @@ public class WeaponModule : ActiveModule private readonly ModuleAction _action; public ModuleProperty DamageModifier { get; } + + public ModuleProperty RemoteControlDamageModifier { get; } + public ModuleProperty Accuracy { get; } public WeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) @@ -25,6 +28,8 @@ public WeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, t _action = new ModuleAction(this); DamageModifier = new ModuleProperty(this,AggregateField.damage_modifier); AddProperty(DamageModifier); + RemoteControlDamageModifier = new ModuleProperty(this, AggregateField.remote_control_damage_modifier); + AddProperty(DamageModifier); Accuracy = new ModuleProperty(this, AggregateField.accuracy); AddProperty(Accuracy); From 8bb43fc47f8f178f2311dbcb605bf22f3e46c499 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Thu, 2 Nov 2023 02:20:11 +0500 Subject: [PATCH 403/457] Refactoring AI part 1 --- .../PerpetuumBootstrapper.cs | 3 +- src/Perpetuum/Modules/RemoteController.cs | 2 +- src/Perpetuum/Perpetuum.csproj | 18 + src/Perpetuum/Units/UnitExtensions.cs | 60 ++-- .../Zones/NpcSystem/AI/SmartCreatureAI.cs | 51 +++ .../AI/SmartCreatureAggressiveBehavior.cs | 15 + .../NpcSystem/AI/SmartCreatureAggressorAI.cs | 253 ++++++++++++++ .../NpcSystem/AI/SmartCreatureBehavior.cs | 29 ++ .../NpcSystem/AI/SmartCreatureBehaviorType.cs | 10 + .../AI/SmartCreatureBodyPullThreatHelper.cs | 107 ++++++ .../NpcSystem/AI/SmartCreatureCombatAI.cs | 280 +++++++++++++++ .../NpcSystem/AI/SmartCreatureHomingAI.cs | 75 ++++ .../Zones/NpcSystem/AI/SmartCreatureIdleAI.cs | 43 +++ .../AI/SmartCreatureNeutralBehavior.cs | 15 + .../AI/SmartCreaturePassiveBehavior.cs | 9 + .../SmartCreatureRemoteControlledBehavior.cs | 9 + .../AI/SmartCreatureStationaryCombatAI.cs | 44 +++ .../AI/SmartCreatureStationaryIdleAI.cs | 36 ++ src/Perpetuum/Zones/NpcSystem/Npc.cs | 102 +++--- .../NpcSystem/PrimaryLockSelectionStrategy.cs | 10 + .../PrimaryLockSelectionStrategyBuilder.cs | 26 ++ .../PrimaryLockSelectionStrategySelector.cs | 34 ++ .../Zones/NpcSystem/PrimaryTargetStrategy.cs | 116 ++++--- .../Zones/NpcSystem/SmartCreature.cs | 324 ++++++++++++++++++ .../Zones/RemoteControl/BandwidthHandler.cs | 2 +- .../Zones/RemoteControl/SentryTurret.cs | 32 +- 26 files changed, 1565 insertions(+), 140 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureIdleAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/SmartCreature.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 107b8dcfc..4cd2cc73c 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -848,6 +848,7 @@ private void RegisterEntities() RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); RegisterRobot(); RegisterRobot(); + RegisterRobot(); _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); @@ -988,7 +989,7 @@ private void RegisterEntities() RegisterEntity(); RegisterUnit(); - RegisterUnit(); + //RegisterUnit(); RegisterUnit(); RegisterUnit(); RegisterUnit(); diff --git a/src/Perpetuum/Modules/RemoteController.cs b/src/Perpetuum/Modules/RemoteController.cs index 1dc43be71..fd2c6a8a3 100644 --- a/src/Perpetuum/Modules/RemoteController.cs +++ b/src/Perpetuum/Modules/RemoteController.cs @@ -15,7 +15,7 @@ namespace Perpetuum.Modules public class RemoteControllerModule : ActiveModule { private const int SentryTurretHeight = 7; - private const double SentryTurretDeployRange = 5; + private const double SentryTurretDeployRange = 2; private readonly ModuleProperty bandwidthMax; private BandwidthHandler bandwidthHandler; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 417f79cd6..98280936e 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -653,6 +653,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Perpetuum/Units/UnitExtensions.cs b/src/Perpetuum/Units/UnitExtensions.cs index 24f8af8f3..f4b7092c5 100644 --- a/src/Perpetuum/Units/UnitExtensions.cs +++ b/src/Perpetuum/Units/UnitExtensions.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using Perpetuum.ExportedTypes; using Perpetuum.Log; using Perpetuum.Players; @@ -7,6 +5,8 @@ using Perpetuum.Zones; using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.PBS; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Units { @@ -21,23 +21,28 @@ 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 +56,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 +75,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 +97,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 +105,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 +125,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 +138,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 +155,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,17 +176,24 @@ 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()) { - npc.AddAssistThreat(assistant,unit,threat); + npc.AddAssistThreat(assistant, unit, threat); + } + + foreach (var smartCreature in unit.GetWitnessUnits()) + { + smartCreature.AddAssistThreat(assistant, unit, threat); } } - + public static bool IsPlayer(this Unit unit) { return unit is Player; } @@ -196,12 +208,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 +247,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/Zones/NpcSystem/AI/SmartCreatureAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAI.cs new file mode 100644 index 000000000..0e35eb341 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAI.cs @@ -0,0 +1,51 @@ +using Perpetuum.Log; +using Perpetuum.StateMachines; +using System; +using System.Diagnostics; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public abstract class SmartCreatureAI : IState + { + protected readonly SmartCreature smartCreature; + + protected SmartCreatureAI(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 SmartCreatureHomingAI(smartCreature)); + } + + protected virtual void ToAggressorAI() + { + if (this.smartCreature.Behavior.Type == SmartCreatureBehaviorType.Passive) + { + return; + } + + this.smartCreature.AI.Push(new SmartCreatureAggressorAI(smartCreature)); + } + + [Conditional("DEBUG")] + protected void WriteLog(string message) + { + Logger.DebugInfo($"SmartCreatureAI: {message}"); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs new file mode 100644 index 000000000..eb8305572 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureAggressiveBehavior : SmartCreatureBehavior + { + public SmartCreatureAggressiveBehavior() : base(SmartCreatureBehaviorType.Aggressive) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs new file mode 100644 index 000000000..e831d2e57 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs @@ -0,0 +1,253 @@ +using Perpetuum.Collections; +using Perpetuum.PathFinders; +using Perpetuum.Units; +using Perpetuum.Zones.Movements; +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 SmartCreatureAggressorAI : SmartCreatureCombatAI + { + private Position lastTargetPosition; + private PathMovement movement; + private PathMovement nextMovement; + private CancellationTokenSource source; + private const int Sqrt2 = 141; + private const int Weight = 1000; + + public SmartCreatureAggressorAI(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 SmartCreatureHomingAI(smartCreature)); + + return; + } + + if (!smartCreature.ThreatManager.IsThreatened) + { + EnterEvadeMode(); + + return; + } + + this.UpdateHostile(time); + + base.Update(time); + } + + protected override void ToAggressorAI() { } + + private void EnterEvadeMode() + { + smartCreature.AI.Pop(); + smartCreature.AI.Push(new SmartCreatureHomingAI(smartCreature)); + this.WriteLog("Enter evade mode."); + } + + private void UpdateHostile(TimeSpan time) + { + var mostHated = GetPrimaryOrMostHatedHostile(); + + if (mostHated == null) + { + return; + } + + if (!mostHated.unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) + { + this.lastTargetPosition = mostHated.unit.CurrentPosition; + + var findNewTargetPosition = false; + + if (!smartCreature.IsInRangeOf3D(mostHated.unit, smartCreature.BestCombatRange)) + { + 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) + { + 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 Task> FindNewAttackPositionAsync(Unit hostile) + { + this.source?.Cancel(); + this.source = new CancellationTokenSource(); + + return Task.Run(() => FindNewAttackPosition(hostile, this.source.Token), this.source.Token); + } + + private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) + { + var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestCombatRange - 1).ToPoint(); + + smartCreature.StopMoving(); + + 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(); + + closed.Add(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.BestCombatRange)) + { + 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(); + } + + private 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/SmartCreatureBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs new file mode 100644 index 000000000..8a05bf260 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs @@ -0,0 +1,29 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureBehavior + { + public SmartCreatureBehaviorType Type { get; private set; } + + protected SmartCreatureBehavior(SmartCreatureBehaviorType type) + { + Type = type; + } + + public static SmartCreatureBehavior Create(SmartCreatureBehaviorType type) + { + switch (type) + { + case SmartCreatureBehaviorType.Neutral: + return new SmartCreatureNeutralBehavior(); + case SmartCreatureBehaviorType.Aggressive: + return new SmartCreatureAggressiveBehavior(); + case SmartCreatureBehaviorType.Passive: + return new SmartCreaturePassiveBehavior(); + case SmartCreatureBehaviorType.RemoteControlled: + return new SmartCreatureRemoteControlledBehavior(); + default: + return new SmartCreaturePassiveBehavior(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs new file mode 100644 index 000000000..c4479c648 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs @@ -0,0 +1,10 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public enum SmartCreatureBehaviorType + { + Passive, + Neutral, + Aggressive, + RemoteControlled, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs new file mode 100644 index 000000000..a59eefc6b --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs @@ -0,0 +1,107 @@ +using Perpetuum.EntityFramework; +using Perpetuum.Modules.Weapons; +using Perpetuum.Players; +using Perpetuum.Units; +using Perpetuum.Zones.Eggs; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureBodyPullThreatHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor + { + private readonly SmartCreature smartCreature; + + public SmartCreatureBodyPullThreatHelper(SmartCreature smartCreature) + { + this.smartCreature = smartCreature; + } + + public void Visit(Player player) + { + if (smartCreature.Behavior.Type != SmartCreatureBehaviorType.Aggressive) + { + return; + } + + if (player.HasTeleportSicknessEffect) + { + return; + } + + if (smartCreature.ThreatManager.Hostiles.Any(h => h.unit.Eid == player.Eid)) + { + return; + } + + if (!smartCreature.IsInAggroRange(player)) + { + return; + } + + var 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; + } + + var group = smartCreature.Group; + + if (group != null && group.Members.Any(m => m.ThreatManager.Contains(bomb))) + { + return; + } + + var threat = Threat.BODY_PULL; + + if (smartCreature.ThreatManager.IsThreatened) + { + var 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) + { + if (smartCreature.Behavior.Type != SmartCreatureBehaviorType.RemoteControlled) + { + return; + } + + if (!smartCreature.ActiveModules.Any(m => m is WeaponModule)) + { + return; + } + + if (smartCreature.ThreatManager.Hostiles.Any(h => h.unit.Eid == npc.Eid)) + { + return; + } + + if (!smartCreature.IsInAggroRange(npc)) + { + return; + } + + var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + + smartCreature.AddThreat(npc, new Threat(ThreatType.Bodypull, threat)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs new file mode 100644 index 000000000..f8b459281 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs @@ -0,0 +1,280 @@ +using Perpetuum.Modules.Weapons; +using Perpetuum.Timers; +using Perpetuum.Zones.Locking; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Terrains; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureCombatAI : SmartCreatureAI + { + private const int UpdateFrequency = 1650; + private readonly IntervalTimer processHostilesTimer = new IntervalTimer(UpdateFrequency); + private readonly IntervalTimer primarySelectTimer = new IntervalTimer(UpdateFrequency); + private List moduleActivators; + private TimeSpan hostilesUpdateFrequency = TimeSpan.FromMilliseconds(UpdateFrequency); + private PrimaryLockSelectionStrategySelector stratSelector; + + public bool IsNpcHasMissiles { get; set; } = false; + + public SmartCreatureCombatAI(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 PrimaryLockSelectionStrategySelector InitSelector() + { + return PrimaryLockSelectionStrategySelector.Create() + .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 9) + .WithStrategy(PrimaryLockSelectionStrategy.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 == SmartCreatureBehaviorType.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; + } + + var weakestLock = this.smartCreature.ThreatManager.Hostiles + .SkipWhile(h => h != hostile) + .Skip(1) + .Select(h => this.smartCreature.GetLockByUnit(h.unit)) + .LastOrDefault(); + + if (weakestLock == null) + { + return false; + } + + weakestLock.Cancel(); + + return true; + } + + protected Hostile GetPrimaryOrMostHatedHostile() + { + var primaryHostile = this.smartCreature.ThreatManager.Hostiles + .Where(h => h.unit == (this.smartCreature.GetPrimaryLock() as UnitLock)?.Target) + .FirstOrDefault(); + + if (primaryHostile != null) + { + return primaryHostile; + } + + return this.smartCreature.ThreatManager.GetMostHatedHostile(); + } + + private void SetLockForHostile(Hostile hostile) + { + var mostHated = GetPrimaryOrMostHatedHostile() == hostile; + var l = this.smartCreature.GetLockByUnit(hostile.unit); + + if (l == null) + { + if (TryMakeFreeLockSlotFor(hostile)) + { + this.smartCreature.AddLock(hostile.unit, mostHated); + } + } + else + { + if (mostHated && !l.Primary) + { + this.smartCreature.SetPrimaryLock(l.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.MaxCombatRange; + } + + 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; + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs new file mode 100644 index 000000000..a43a76a8a --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs @@ -0,0 +1,75 @@ +using Perpetuum.PathFinders; +using Perpetuum.Zones.Movements; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureHomingAI : SmartCreatureCombatAI + { + private PathMovement movement; + private readonly double maxReturnHomeRadius; + private readonly PathFinder pathFinder; + + public SmartCreatureHomingAI(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/SmartCreatureIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureIdleAI.cs new file mode 100644 index 000000000..13dfbab98 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureIdleAI.cs @@ -0,0 +1,43 @@ +using Perpetuum.Zones.Movements; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureIdleAI : SmartCreatureAI + { + private RandomMovement movement; + + public SmartCreatureIdleAI(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/SmartCreatureNeutralBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs new file mode 100644 index 000000000..c997bf1f2 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureNeutralBehavior : SmartCreatureBehavior + { + public SmartCreatureNeutralBehavior() : base(SmartCreatureBehaviorType.Neutral) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs new file mode 100644 index 000000000..5f1addba4 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreaturePassiveBehavior : SmartCreatureBehavior + { + public SmartCreaturePassiveBehavior() : base(SmartCreatureBehaviorType.Passive) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs new file mode 100644 index 000000000..763cf7873 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureRemoteControlledBehavior : SmartCreatureBehavior + { + public SmartCreatureRemoteControlledBehavior() : base(SmartCreatureBehaviorType.RemoteControlled) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs new file mode 100644 index 000000000..488f489d4 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs @@ -0,0 +1,44 @@ +using Perpetuum.Timers; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureStationaryCombatAI : SmartCreatureCombatAI + { + private readonly IntervalTimer updateFrequency = new IntervalTimer(650); + public SmartCreatureStationaryCombatAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override PrimaryLockSelectionStrategySelector InitSelector() + { + return PrimaryLockSelectionStrategySelector.Create() + .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 1) + .WithStrategy(PrimaryLockSelectionStrategy.Closest, 2) + .WithStrategy(PrimaryLockSelectionStrategy.OptimalRange, 3) + .WithStrategy(PrimaryLockSelectionStrategy.Random, 10) + .Build(); + } + + public override void Update(TimeSpan time) + { + FindHostiles(time); + + base.Update(time); + } + + private void FindHostiles(TimeSpan time) + { + updateFrequency.Update(time); + + if (updateFrequency.Passed) + { + updateFrequency.Reset(); + this.smartCreature.LookingForHostiles(); + } + } + + protected override TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs new file mode 100644 index 000000000..c57598d26 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs @@ -0,0 +1,36 @@ +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SmartCreatureStationaryIdleAI : SmartCreatureAI + { + public SmartCreatureStationaryIdleAI(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 == SmartCreatureBehaviorType.Passive) + { + return; + } + + this.smartCreature.AI.Push(new SmartCreatureStationaryCombatAI(this.smartCreature)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 59ace8a2c..bc8acbcbe 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -1,11 +1,3 @@ -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; @@ -23,12 +15,20 @@ using Perpetuum.Units; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Eggs; -using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Movements; using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains; +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; namespace Perpetuum.Zones.NpcSystem { @@ -159,10 +159,10 @@ public StationaryCombatAI(Npc npc) : base(npc) { } protected override PrimaryLockSelectionStrategySelector InitSelector() { return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockStrategy.Hostile, 1) - .WithStrategy(PrimaryLockStrategy.Closest, 2) - .WithStrategy(PrimaryLockStrategy.OptimalRange, 3) - .WithStrategy(PrimaryLockStrategy.Random, 10) + .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 1) + .WithStrategy(PrimaryLockSelectionStrategy.Closest, 2) + .WithStrategy(PrimaryLockSelectionStrategy.OptimalRange, 3) + .WithStrategy(PrimaryLockSelectionStrategy.Random, 10) .Build(); } @@ -202,8 +202,8 @@ public CombatAI(Npc npc) : base(npc) { } protected virtual PrimaryLockSelectionStrategySelector InitSelector() { return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockStrategy.Hostile, 9) - .WithStrategy(PrimaryLockStrategy.Random, 1) + .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 9) + .WithStrategy(PrimaryLockSelectionStrategy.Random, 1) .Build(); } @@ -413,7 +413,7 @@ public HomingAI(Npc npc) : base(npc) public override void Enter() { var randomHome = npc.Zone.FindPassablePointInRadius(npc.HomePosition, (int)_maxReturnHomeRadius); - if(randomHome == default) + if (randomHome == default) { randomHome = npc.HomePosition; } @@ -424,7 +424,7 @@ public override void Enter() { WriteLog("Path not found! (" + npc.CurrentPosition + " => " + npc.HomePosition + ")"); - var f = new AStarFinder(Heuristic.Manhattan,(x,y) => true); + var f = new AStarFinder(Heuristic.Manhattan, (x, y) => true); path = f.FindPath(npc.CurrentPosition, npc.HomePosition); if (path == null) @@ -463,9 +463,9 @@ protected override void ToAggressorAI() { } public class AggressorAI : CombatAI { - public AggressorAI(Npc npc) : base(npc){ } + public AggressorAI(Npc npc) : base(npc) { } - protected override void ToAggressorAI(){ } + protected override void ToAggressorAI() { } public override void Exit() { @@ -546,7 +546,7 @@ private void UpdateHostile(TimeSpan time) return; } - Interlocked.Exchange(ref _nextMovement,new PathMovement(path)); + Interlocked.Exchange(ref _nextMovement, new PathMovement(path)); }); } } @@ -573,14 +573,14 @@ private Task> FindNewAttackPositionAsync(Unit hostile) return Task.Run(() => FindNewAttackPosition(hostile, _source.Token), _source.Token); } - private List FindNewAttackPosition(Unit hostile,CancellationToken cancellationToken) + private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) { 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 pq = new PriorityQueue((int)maxNode); var startNode = new Node(npc.CurrentPosition); pq.Enqueue(startNode); @@ -627,7 +627,7 @@ private List FindNewAttackPosition(Unit hostile,CancellationToken cancell return null; } - private bool IsValidAttackPosition(Unit hostile,Point position) + private bool IsValidAttackPosition(Unit hostile, Point position) { var position3 = npc.Zone.FixZ(position.ToPosition()).AddToZ(npc.Height); @@ -792,9 +792,9 @@ public bool IsStationary get { return MaxSpeed.IsZero(); } } - public void Tag(Player tagger,TimeSpan duration) + public void Tag(Player tagger, TimeSpan duration) { - _tagHelper.DoTagging(this,tagger,duration); + _tagHelper.DoTagging(this, tagger, duration); } [CanBeNull] @@ -826,7 +826,7 @@ public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) { if (member == this) continue; - member.AddThreat(hostile,t,false); + member.AddThreat(hostile, t, false); } } @@ -864,21 +864,21 @@ protected override void OnPropertyChanged(ItemProperty property) switch (property.Field) { case AggregateField.locking_range: - { - _optimalCombatRange = new Lazy(CalculateCombatRange); - _maxCombatRange = new Lazy(CalculateMaxCombatRange); - break; - } - case AggregateField.armor_current: - { - var percentage = Armor.Ratio(ArmorMax); - if (percentage <= CALL_FOR_HELP_ARMOR_THRESHOLD) { - CallingForHelp(); + _optimalCombatRange = new Lazy(CalculateCombatRange); + _maxCombatRange = new Lazy(CalculateMaxCombatRange); + break; } + case AggregateField.armor_current: + { + var percentage = Armor.Ratio(ArmorMax); + if (percentage <= CALL_FOR_HELP_ARMOR_THRESHOLD) + { + CallingForHelp(); + } - break; - } + break; + } } } @@ -903,7 +903,7 @@ public override IDictionary GetDebugInfo() _group?.AddDebugInfoToDictionary(info); - info.Add("ismission",GetMissionGuid() != Guid.Empty); + info.Add("ismission", GetMissionGuid() != Guid.Empty); return info; } @@ -983,7 +983,7 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) if (EP > 0) { var awardedPlayers = new List(); - foreach (var hostile in ThreatManager.Hostiles.Where(x=>x.unit is Player)) + foreach (var hostile in ThreatManager.Hostiles.Where(x => x.unit is Player)) { var playerUnit = hostile.unit; var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); @@ -1032,11 +1032,11 @@ private void SearchForMissionOwnerAndSubmitKill(IZone zone, Unit killerUnit) Task.Run(() => { - MissionHelper.MissionProcessor.NpcGotKilledInAway(missionOwner,missionGuid,info); + MissionHelper.MissionProcessor.NpcGotKilledInAway(missionOwner, missionGuid, info); }); return; } - + //local enqueue, this is the proper player, we can skip gang EnqueueKill(missionOwnerPlayer, killerUnit); } @@ -1219,7 +1219,7 @@ protected override void UpdateUnitVisibility(Unit target) private void AddBodyPullThreat(Unit enemy) { - if ( !IsHostile(enemy)) + if (!IsHostile(enemy)) return; var helper = new BodyPullThreatHelper(this); @@ -1250,23 +1250,23 @@ 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); + AddThreat(hostile.unit, new Threat(ThreatType.Undefined, hostile.Threat), true); } } public void AddAssistThreat(Unit assistant, Unit target, Threat threat) { - if ( !_threatManager.Contains(target) ) + if (!_threatManager.Contains(target)) return; - if ( !CanAddThreatTo(assistant,threat)) + if (!CanAddThreatTo(assistant, threat)) return; - AddThreat(assistant,threat,true); + AddThreat(assistant, threat, true); } private int CalculateCombatRange() @@ -1278,7 +1278,7 @@ private int CalculateCombatRange() range *= BEST_COMBAT_RANGE_MODIFIER; range = Math.Max(3, range); - return (int) range; + return (int)range; } private int CalculateMaxCombatRange() @@ -1293,7 +1293,7 @@ private int CalculateMaxCombatRange() private const double BEST_COMBAT_RANGE_MODIFIER = 0.9; - private class BodyPullThreatHelper : IEntityVisitor,IEntityVisitor + private class BodyPullThreatHelper : IEntityVisitor, IEntityVisitor { private readonly Npc _npc; @@ -1340,7 +1340,7 @@ public void Visit(AreaBomb bomb) { var h = _npc.ThreatManager.GetMostHatedHostile(); if (h != null) - threat = h.Threat*100; + threat = h.Threat * 100; } _npc.AddThreat(bomb, new Threat(ThreatType.Bodypull, threat + FastRandom.NextDouble(0, 5))); diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs new file mode 100644 index 000000000..efabda7f5 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs @@ -0,0 +1,10 @@ +namespace Perpetuum.Zones.NpcSystem +{ + public enum PrimaryLockSelectionStrategy + { + Random, + Hostile, + Closest, + OptimalRange, + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs new file mode 100644 index 000000000..7e1716668 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs @@ -0,0 +1,26 @@ +using Perpetuum.Collections; + +namespace Perpetuum.Zones.NpcSystem +{ + public class PrimaryLockSelectionStrategyBuilder + { + private readonly WeightedCollection selection; + + public PrimaryLockSelectionStrategyBuilder() + { + selection = new WeightedCollection(); + } + + public PrimaryLockSelectionStrategyBuilder WithStrategy(PrimaryLockSelectionStrategy strategy, int weight = 1) + { + selection.Add(strategy, weight); + + return this; + } + + public PrimaryLockSelectionStrategySelector Build() + { + return new PrimaryLockSelectionStrategySelector(selection); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs new file mode 100644 index 000000000..cf02aed6f --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs @@ -0,0 +1,34 @@ +using Perpetuum.Collections; +using Perpetuum.Zones.Locking.Locks; + +namespace Perpetuum.Zones.NpcSystem +{ + public class PrimaryLockSelectionStrategySelector + { + private readonly WeightedCollection primaryLockSelectionStrategies; + + public PrimaryLockSelectionStrategySelector(WeightedCollection primaryLockSelectionStrategies) + { + this.primaryLockSelectionStrategies = primaryLockSelectionStrategies; + } + + public bool TryUseStrategy(Npc npc, UnitLock[] locks) + { + var primaryLockSelectionStrategy = primaryLockSelectionStrategies.GetRandom(); + + return Strategies.TryInvokeStrategy(primaryLockSelectionStrategy, npc, locks); + } + + public bool TryUseStrategy(SmartCreature smartCreature, UnitLock[] locks) + { + var primaryLockSelectionStrategy = primaryLockSelectionStrategies.GetRandom(); + + return Strategies.TryInvokeStrategy(primaryLockSelectionStrategy, smartCreature, locks); + } + + public static PrimaryLockSelectionStrategyBuilder Create() + { + return new PrimaryLockSelectionStrategyBuilder(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs index 981088237..3db5c85df 100644 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs @@ -1,36 +1,42 @@ -using Perpetuum.Collections; -using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Locking.Locks; using System.Collections.Generic; using System.Linq; namespace Perpetuum.Zones.NpcSystem { - public enum PrimaryLockStrategy - { - Random, - Hostile, - Closest, - OptimalRange - } - public delegate bool TargetSelectionStrategy(Npc npc, UnitLock[] locks); + public delegate bool SmartCreatureTargetSelectionStrategy(SmartCreature smartCreature, UnitLock[] locks); public static class Strategies { - public static Dictionary All = new Dictionary() + public static Dictionary All = new Dictionary() + { + { PrimaryLockSelectionStrategy.Random, TargetRandom }, + { PrimaryLockSelectionStrategy.Hostile, TargetMostHated }, + { PrimaryLockSelectionStrategy.Closest, TargetClosest }, + { PrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal } + }; + + public static Dictionary SmartCreatureAll = + new Dictionary() { - { PrimaryLockStrategy.Random, TargetRandom }, - { PrimaryLockStrategy.Hostile, TargetMostHated }, - { PrimaryLockStrategy.Closest, TargetClosest }, - { PrimaryLockStrategy.OptimalRange, TargetWithinOptimal } + { PrimaryLockSelectionStrategy.Random, SmartCreatureTargetRandom }, + { PrimaryLockSelectionStrategy.Hostile, SmartCreatureTargetMostHated }, + { PrimaryLockSelectionStrategy.Closest, SmartCreatureTargetClosest }, + { PrimaryLockSelectionStrategy.OptimalRange, SmartCreatureTargetWithinOptimal } }; - public static TargetSelectionStrategy GetStrategy(PrimaryLockStrategy strategyType) + public static TargetSelectionStrategy GetStrategy(PrimaryLockSelectionStrategy strategyType) { return All.GetOrDefault(strategyType); } - public static bool TryInvokeStrategy(PrimaryLockStrategy strategyType, Npc npc, UnitLock[] locks) + public static SmartCreatureTargetSelectionStrategy SmartCreatureGetStrategy(PrimaryLockSelectionStrategy strategyType) + { + return SmartCreatureAll.GetOrDefault(strategyType); + } + + public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, Npc npc, UnitLock[] locks) { var strat = GetStrategy(strategyType); if (strat == null) @@ -38,6 +44,18 @@ public static bool TryInvokeStrategy(PrimaryLockStrategy strategyType, Npc npc, return strat(npc, locks); } + public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, SmartCreature smartCreature, UnitLock[] locks) + { + var primaryLockSelectionStrategy = SmartCreatureGetStrategy(strategyType); + + if (primaryLockSelectionStrategy == null) + { + return false; + } + + return primaryLockSelectionStrategy(smartCreature, locks); + } + private static bool TargetMostHated(Npc npc, UnitLock[] locks) { var hostiles = npc.ThreatManager.Hostiles; @@ -46,65 +64,63 @@ private static bool TargetMostHated(Npc npc, UnitLock[] locks) return TrySetPrimaryLock(npc, mostHostileLock); } + private static bool SmartCreatureTargetMostHated(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 SmartCreatureTrySetPrimaryLock(smartCreature, mostHostileLock); + } + private static bool TargetWithinOptimal(Npc npc, UnitLock[] locks) { return TrySetPrimaryLock(npc, locks.Where(k => k.Target.GetDistance(npc) < npc.BestCombatRange).RandomElement()); } + private static bool SmartCreatureTargetWithinOptimal(SmartCreature smartCreature, UnitLock[] locks) + { + return SmartCreatureTrySetPrimaryLock(smartCreature, locks.Where(k => k.Target.GetDistance(smartCreature) < smartCreature.BestCombatRange).RandomElement()); + } + private static bool TargetClosest(Npc npc, UnitLock[] locks) { return TrySetPrimaryLock(npc, locks.OrderBy(u => u.Target.GetDistance(npc)).First()); } + private static bool SmartCreatureTargetClosest(SmartCreature smartCreature, UnitLock[] locks) + { + return SmartCreatureTrySetPrimaryLock(smartCreature, locks.OrderBy(u => u.Target.GetDistance(smartCreature)).First()); + } + private static bool TargetRandom(Npc npc, UnitLock[] locks) { return TrySetPrimaryLock(npc, locks.RandomElement()); } + private static bool SmartCreatureTargetRandom(SmartCreature smartCreature, UnitLock[] locks) + { + return SmartCreatureTrySetPrimaryLock(smartCreature, locks.RandomElement()); + } + private static bool TrySetPrimaryLock(Npc npc, Lock l) { if (l == null) return false; npc.SetPrimaryLock(l); return true; } - } - - - - public class PrimaryLockSelectionStrategySelector - { - private readonly WeightedCollection _selection; - public PrimaryLockSelectionStrategySelector(WeightedCollection selection) - { - _selection = selection; - } - public bool TryUseStrategy(Npc npc, UnitLock[] locks) - { - var stratType = _selection.GetRandom(); - return Strategies.TryInvokeStrategy(stratType, npc, locks); - } - public static PrimaryLockSelectionStrategyBuilder Create() - { - return new PrimaryLockSelectionStrategyBuilder(); - } - public class PrimaryLockSelectionStrategyBuilder + private static bool SmartCreatureTrySetPrimaryLock(SmartCreature smartCreature, Lock targetLock) { - private readonly WeightedCollection _selection; - public PrimaryLockSelectionStrategyBuilder() + if (targetLock == null) { - _selection = new WeightedCollection(); - } - public PrimaryLockSelectionStrategyBuilder WithStrategy(PrimaryLockStrategy strategy, int weight = 1) - { - _selection.Add(strategy, weight); - return this; + return false; } + smartCreature.SetPrimaryLock(targetLock); - public PrimaryLockSelectionStrategySelector Build() - { - return new PrimaryLockSelectionStrategySelector(_selection); - } + return true; } } } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs new file mode 100644 index 000000000..57624afe9 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -0,0 +1,324 @@ +using Perpetuum.EntityFramework; +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.Flocks; +using System; +using System.Linq; + +namespace Perpetuum.Zones.NpcSystem +{ + #region move to separate files + + + + #endregion + + public class SmartCreature : Creature + { + private const double AggroRange = 30; + private const double BestComnatRangeModifier = 0.9; + private readonly ThreatManager threatManager; + private readonly IPseudoThreatManager pseudoThreatManager; + private readonly Lazy maxCombatRange; + private readonly Lazy optimalCombatRange; + 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)); + + [CanBeNull] + private INpcGroup group; + + public StackFSM AI { get; private set; } + + public SmartCreatureBehavior Behavior { get; set; } + + public double HomeRange { get; set; } + + public Position HomePosition { get; set; } + + public IThreatManager ThreatManager + { + get { return this.threatManager; } + } + + public int BestCombatRange + { + get { return this.optimalCombatRange.Value; } + } + + public int MaxCombatRange + { + get { return this.maxCombatRange.Value; } + } + + public bool IsInHomeRange + { + get { return CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); } + } + + public INpcGroup Group + { + get { return group; } + } + + public virtual bool IsStationary + { + get { return MaxSpeed.IsZero(); } + } + + public SmartCreature() + { + maxCombatRange = new Lazy(CalculateMaxCombatRange); + optimalCombatRange = new Lazy(CalculateCombatRange); + threatManager = new ThreatManager(); + AI = new StackFSM(); + pseudoThreatManager = new PseudoThreatManager(); + } + + public void LookingForHostiles() + { + foreach (var visibility in GetVisibleUnits()) + { + AddBodyPullThreat(visibility.Target); + } + } + + public void AddPseudoThreat(Unit hostile) + { + pseudoThreatManager.AddOrRefreshExisting(hostile); + } + + public void SetGroup(INpcGroup group) + { + this.group = group; + } + + protected override void OnUnitLockStateChanged(Lock @lock) + { + if (!debounceLockChange.Expired) + { + return; + } + + 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); + debounceLockChange.Reset(); + } + + protected override void OnUnitTileChanged(Unit target) + { + if (!debounceBodyPull.Expired) + { + return; + } + + AddBodyPullThreat(target); + debounceBodyPull.Reset(); + } + + private void AddBodyPullThreat(Unit enemy) + { + if (!IsHostile(enemy)) + { + return; + } + + var helper = new SmartCreatureBodyPullThreatHelper(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 IsStationary || IsInRangeOf3D(target, AggroRange); + } + + public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) + { + //if (hostile.IsPlayer()) + //{ + // BossInfo?.OnAggro(hostile as Player); + //} + + threatManager.GetOrAddHostile(hostile).AddThreat(threat); + + RemovePseudoThreat(hostile); + + if (!spreadToGroup) + { + return; + } + + var group = Group; + if (@group == null) + { + return; + } + + //var multipliedThreat = Threat.Multiply(threat, 0.5); + + //foreach (var 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) + { + if (threatManager.Contains(target)) + { + return true; + } + + if (Behavior.Type == SmartCreatureBehaviorType.Passive) + { + return false; + } + + return 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); + } + + protected override void OnTileChanged() + { + base.OnTileChanged(); + LookingForHostiles(); + } + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + States.Aggressive = Behavior.Type == SmartCreatureBehaviorType.Aggressive; + + base.OnEnterZone(zone, enterType); + + if (IsStationary) + { + AI.Push(new SmartCreatureStationaryIdleAI(this)); + } + else + { + AI.Push(new SmartCreatureIdleAI(this)); + } + } + + protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) + { + base.OnDamageTaken(source, e); + + var player = Zone.ToPlayerOrGetOwnerPlayer(source); + if (player == null) + { + return; + } + + //BossInfo?.OnDamageTaken(this, player); + 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(); + } + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index 40bf924e8..2ce746ac2 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -47,7 +47,7 @@ public List Turrets public bool HasFreeBandwidthFor(RemoteControlledUnit unit) { - return BandwidthUsed <= owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; + return BandwidthUsed < owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; } public void UseRemoteChannel(SentryTurret turret) diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index b99ef4631..645f7b0e2 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -1,23 +1,17 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; -using Perpetuum.Items.Ammos; using Perpetuum.Players; -using Perpetuum.Robots; using Perpetuum.Units; -using Perpetuum.Zones.Eggs; -using Perpetuum.Zones.LandMines; +using Perpetuum.Zones.Beams; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.AI; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Perpetuum.Zones.RemoteControl { - public class SentryTurret : Turret + public class SentryTurret : SmartCreature { - private UnitDespawnHelper _despawnHelper; + private UnitDespawnHelper despawnHelper; public event RemoteChannelEventHandler RemoteChannelDeactivated; @@ -30,21 +24,24 @@ public double RemoteChannelBandwidthUsage public TimeSpan DespawnTime { - set { _despawnHelper = UnitDespawnHelper.Create(this, value); } + set { despawnHelper = UnitDespawnHelper.Create(this, value); } } public override void Initialize() { rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); AddProperty(rcBandwidthUsage); + Behavior = SmartCreatureBehavior.Create(SmartCreatureBehaviorType.RemoteControlled); base.Initialize(); } + public override bool IsStationary => true; + protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); - _despawnHelper?.Update(time, this); + despawnHelper?.Update(time, this); } protected override bool IsHostileFor(Unit unit) @@ -75,5 +72,16 @@ protected override void OnRemovedFromZone(IZone zone) RemoteChannelDeactivated(this); base.OnRemovedFromZone(zone); } + + protected override void OnDead(Unit killer) + { + Zone.CreateBeam( + BeamType.arbalest_wreck, + builder => builder + .WithPosition(CurrentPosition) + .WithState(BeamState.Hit)); + + base.OnDead(killer); + } } } From df668e504a692eddb11f34a63fe0bb4b2cfcf31f Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Thu, 2 Nov 2023 22:06:40 +0500 Subject: [PATCH 404/457] Refactoring AI part 2 --- src/Perpetuum/Perpetuum.csproj | 32 +- .../EventMessages/NpcReinforcementsMessage.cs | 7 +- .../NpcReinforcementSpawner.cs | 22 +- .../Zones/NpcSystem/AI/AggressiveBehavior.cs | 9 + ...tCreatureAggressorAI.cs => AggressorAI.cs} | 8 +- .../AI/{SmartCreatureAI.cs => BaseAI.cs} | 10 +- src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs | 29 + ...reatureBehaviorType.cs => BehaviorType.cs} | 2 +- ...hreatHelper.cs => BodyPullThreatHelper.cs} | 8 +- .../{SmartCreatureCombatAI.cs => CombatAI.cs} | 6 +- .../{SmartCreatureHomingAI.cs => HomingAI.cs} | 4 +- .../AI/{SmartCreatureIdleAI.cs => IdleAI.cs} | 4 +- .../Zones/NpcSystem/AI/NeutralBehavior.cs | 9 + .../Zones/NpcSystem/AI/PassiveBehavior.cs | 9 + .../NpcSystem/AI/RemoteControlledBehavior.cs | 9 + .../AI/SmartCreatureAggressiveBehavior.cs | 15 - .../NpcSystem/AI/SmartCreatureBehavior.cs | 29 - .../AI/SmartCreatureNeutralBehavior.cs | 15 - .../AI/SmartCreaturePassiveBehavior.cs | 9 - .../SmartCreatureRemoteControlledBehavior.cs | 9 - ...onaryCombatAI.cs => StationaryCombatAI.cs} | 4 +- ...tationaryIdleAI.cs => StationaryIdleAI.cs} | 8 +- src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 215 ++- .../NpcSystem/Flocks/FlockConfiguration.cs | 27 +- .../Flocks/FlockConfigurationRepository.cs | 25 +- .../NpcSystem/Flocks/IFlockConfiguration.cs | 23 +- .../NpcSystem/Flocks/ISmartCreatureGroup.cs | 13 + src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 201 +-- src/Perpetuum/Zones/NpcSystem/Npc.cs | 1370 ++--------------- .../Zones/NpcSystem/NpcBossInfoBuilder.cs | 76 + .../Zones/NpcSystem/NpcSpecialType.cs | 8 + .../Presences/PathFinders/RoamingState.cs | 60 + .../Presences/PathFinders/RoamingStates.cs | 43 - .../Zones/NpcSystem/Presences/Presence.cs | 169 +- .../Zones/NpcSystem/SmartCreature.cs | 117 +- .../Zones/NpcSystem/ThreatManager.cs | 27 +- .../Zones/RemoteControl/SentryTurret.cs | 2 +- 37 files changed, 897 insertions(+), 1736 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureAggressorAI.cs => AggressorAI.cs} (95%) rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureAI.cs => BaseAI.cs} (71%) create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureBehaviorType.cs => BehaviorType.cs} (76%) rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureBodyPullThreatHelper.cs => BodyPullThreatHelper.cs} (87%) rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureCombatAI.cs => CombatAI.cs} (96%) rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureHomingAI.cs => HomingAI.cs} (93%) rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureIdleAI.cs => IdleAI.cs} (86%) create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureStationaryCombatAI.cs => StationaryCombatAI.cs} (87%) rename src/Perpetuum/Zones/NpcSystem/AI/{SmartCreatureStationaryIdleAI.cs => StationaryIdleAI.cs} (63%) create mode 100644 src/Perpetuum/Zones/NpcSystem/Flocks/ISmartCreatureGroup.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/NpcBossInfoBuilder.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/NpcSpecialType.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingState.cs diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 98280936e..a967a817a 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -653,20 +653,24 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs index 2f85f73ed..15371152b 100644 --- a/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs +++ b/src/Perpetuum/Services/EventServices/EventMessages/NpcReinforcementsMessage.cs @@ -6,13 +6,14 @@ public class NpcReinforcementsMessage : IEventMessage { public EventType Type => EventType.NpcReinforce; - public Npc Npc { get; } + public SmartCreature SmartCreature { get; } + public int ZoneId { get; } - public NpcReinforcementsMessage(Npc npc, int zoneID) + public NpcReinforcementsMessage(SmartCreature smartCreature, int zoneID) { ZoneId = zoneID; - Npc = npc; + SmartCreature = smartCreature; } } } diff --git a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs index d3078dfae..dd969976f 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -44,7 +44,7 @@ protected override bool CheckMessage(IEventMessage inMsg, out NpcReinforcementsM protected override void CheckReinforcements(NpcReinforcementsMessage msg) { - var info = msg.Npc.BossInfo; + var info = msg.SmartCreature.BossInfo; if (!_reinforcementsByNpc.ContainsKey(info)) { var reinforcements = _npcReinforcementsRepo.CreateNpcBossAddSpawn(info, msg.ZoneId); @@ -54,7 +54,7 @@ protected override void CheckReinforcements(NpcReinforcementsMessage msg) protected override bool CheckState(NpcReinforcementsMessage msg) { - if (msg.Npc.BossInfo.IsDead) + if (msg.SmartCreature.BossInfo.IsDead) { CleanupAllReinforcements(msg); return true; @@ -65,20 +65,20 @@ protected override bool CheckState(NpcReinforcementsMessage msg) private void UpdateAggro(NpcReinforcementsMessage msg) { - var info = msg.Npc.BossInfo; + 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.Npc); + SpreadAggro(wave.ActivePresence, msg.SmartCreature); } } } protected override void CleanupAllReinforcements(NpcReinforcementsMessage msg) { - var info = msg.Npc.BossInfo; + var info = msg.SmartCreature.BossInfo; if (_reinforcementsByNpc.ContainsKey(info)) { var activeWaves = _reinforcementsByNpc[info].GetAllActiveWaves(); @@ -92,31 +92,31 @@ protected override void CleanupAllReinforcements(NpcReinforcementsMessage msg) protected override Position FindSpawnPosition(NpcReinforcementsMessage msg, int maxRange) { - var finder = new RandomWalkableAroundPositionFinder(_zone, msg.Npc.CurrentPosition, maxRange); + var finder = new RandomWalkableAroundPositionFinder(_zone, msg.SmartCreature.CurrentPosition, maxRange); if (finder.Find(out Position result)) { return result; } - return msg.Npc.CurrentPosition; + return msg.SmartCreature.CurrentPosition; } protected override INpcReinforcementWave GetNextWave(NpcReinforcementsMessage msg) { - var npc = msg.Npc; + 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.Npc); + SpreadAggro(pres, msg.SmartCreature); } - private void SpreadAggro(Presence presenceToAggro, Npc npcWithAggro) + private void SpreadAggro(Presence presenceToAggro, SmartCreature smartCreatureWithAggro) { foreach (var npc in presenceToAggro.Flocks.GetMembers()) { - foreach (var threat in npcWithAggro.ThreatManager.Hostiles) + foreach (var threat in smartCreatureWithAggro.ThreatManager.Hostiles) { npc.AddDirectThreat(threat.unit, threat.Threat + FastRandom.NextDouble(5, 10)); } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs new file mode 100644 index 000000000..0ea174c16 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class AggressiveBehavior : Behavior + { + public AggressiveBehavior() : base(BehaviorType.Aggressive) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs similarity index 95% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs index e831d2e57..8a2b5df9a 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressorAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs @@ -11,7 +11,7 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureAggressorAI : SmartCreatureCombatAI + public class AggressorAI : CombatAI { private Position lastTargetPosition; private PathMovement movement; @@ -20,7 +20,7 @@ public class SmartCreatureAggressorAI : SmartCreatureCombatAI private const int Sqrt2 = 141; private const int Weight = 1000; - public SmartCreatureAggressorAI(SmartCreature smartCreature) : base(smartCreature) { } + public AggressorAI(SmartCreature smartCreature) : base(smartCreature) { } public override void Exit() { @@ -33,7 +33,7 @@ public override void Update(TimeSpan time) { if (!smartCreature.IsInHomeRange) { - smartCreature.AI.Push(new SmartCreatureHomingAI(smartCreature)); + smartCreature.AI.Push(new HomingAI(smartCreature)); return; } @@ -55,7 +55,7 @@ protected override void ToAggressorAI() { } private void EnterEvadeMode() { smartCreature.AI.Pop(); - smartCreature.AI.Push(new SmartCreatureHomingAI(smartCreature)); + smartCreature.AI.Push(new HomingAI(smartCreature)); this.WriteLog("Enter evade mode."); } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs similarity index 71% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs index 0e35eb341..8742ce8f4 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs @@ -5,11 +5,11 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public abstract class SmartCreatureAI : IState + public abstract class BaseAI : IState { protected readonly SmartCreature smartCreature; - protected SmartCreatureAI(SmartCreature smartCreature) + protected BaseAI(SmartCreature smartCreature) { this.smartCreature = smartCreature; } @@ -29,17 +29,17 @@ public virtual void Exit() protected virtual void ToHomeAI() { - this.smartCreature.AI.Push(new SmartCreatureHomingAI(smartCreature)); + this.smartCreature.AI.Push(new HomingAI(smartCreature)); } protected virtual void ToAggressorAI() { - if (this.smartCreature.Behavior.Type == SmartCreatureBehaviorType.Passive) + if (this.smartCreature.Behavior.Type == BehaviorType.Passive) { return; } - this.smartCreature.AI.Push(new SmartCreatureAggressorAI(smartCreature)); + this.smartCreature.AI.Push(new AggressorAI(smartCreature)); } [Conditional("DEBUG")] diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs new file mode 100644 index 000000000..c136503d2 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs @@ -0,0 +1,29 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + 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.RemoteControlled: + return new RemoteControlledBehavior(); + default: + return new PassiveBehavior(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs b/src/Perpetuum/Zones/NpcSystem/AI/BehaviorType.cs similarity index 76% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs rename to src/Perpetuum/Zones/NpcSystem/AI/BehaviorType.cs index c4479c648..d11f7ef19 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehaviorType.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BehaviorType.cs @@ -1,6 +1,6 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public enum SmartCreatureBehaviorType + public enum BehaviorType { Passive, Neutral, diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs similarity index 87% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs rename to src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index a59eefc6b..a470ea43f 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -7,18 +7,18 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureBodyPullThreatHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor + public class BodyPullThreatHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor { private readonly SmartCreature smartCreature; - public SmartCreatureBodyPullThreatHelper(SmartCreature smartCreature) + public BodyPullThreatHelper(SmartCreature smartCreature) { this.smartCreature = smartCreature; } public void Visit(Player player) { - if (smartCreature.Behavior.Type != SmartCreatureBehaviorType.Aggressive) + if (smartCreature.Behavior.Type != BehaviorType.Aggressive) { return; } @@ -79,7 +79,7 @@ public void Visit(AreaBomb bomb) public void Visit(Npc npc) { - if (smartCreature.Behavior.Type != SmartCreatureBehaviorType.RemoteControlled) + if (smartCreature.Behavior.Type != BehaviorType.RemoteControlled) { return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs similarity index 96% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index f8b459281..7687ec222 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureCombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -9,7 +9,7 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureCombatAI : SmartCreatureAI + public class CombatAI : BaseAI { private const int UpdateFrequency = 1650; private readonly IntervalTimer processHostilesTimer = new IntervalTimer(UpdateFrequency); @@ -20,7 +20,7 @@ public class SmartCreatureCombatAI : SmartCreatureAI public bool IsNpcHasMissiles { get; set; } = false; - public SmartCreatureCombatAI(SmartCreature smartCreature) : base(smartCreature) { } + public CombatAI(SmartCreature smartCreature) : base(smartCreature) { } public override void Enter() { @@ -134,7 +134,7 @@ protected bool IsAttackable(Hostile hostile) return false; } - if (this.smartCreature.Behavior.Type == SmartCreatureBehaviorType.Neutral && hostile.IsExpired) + if (this.smartCreature.Behavior.Type == BehaviorType.Neutral && hostile.IsExpired) { return false; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/HomingAI.cs similarity index 93% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/HomingAI.cs index a43a76a8a..81da49e66 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureHomingAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/HomingAI.cs @@ -4,13 +4,13 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureHomingAI : SmartCreatureCombatAI + public class HomingAI : CombatAI { private PathMovement movement; private readonly double maxReturnHomeRadius; private readonly PathFinder pathFinder; - public SmartCreatureHomingAI(SmartCreature smartCreature) : base(smartCreature) + public HomingAI(SmartCreature smartCreature) : base(smartCreature) { this.maxReturnHomeRadius = (smartCreature.HomeRange * 0.4).Clamp(3, 20); this.pathFinder = new AStarFinder(Heuristic.Manhattan, smartCreature.IsWalkable); diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IdleAI.cs similarity index 86% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureIdleAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/IdleAI.cs index 13dfbab98..43e9a5628 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureIdleAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IdleAI.cs @@ -3,11 +3,11 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureIdleAI : SmartCreatureAI + public class IdleAI : BaseAI { private RandomMovement movement; - public SmartCreatureIdleAI(SmartCreature smartCreature) : base(smartCreature) { } + public IdleAI(SmartCreature smartCreature) : base(smartCreature) { } public override void Enter() { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs new file mode 100644 index 000000000..5c9f47368 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class NeutralBehavior : Behavior + { + public NeutralBehavior() : base(BehaviorType.Neutral) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs new file mode 100644 index 000000000..ab2b705f6 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class PassiveBehavior : Behavior + { + public PassiveBehavior() : base(BehaviorType.Passive) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs new file mode 100644 index 000000000..1004a5840 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs @@ -0,0 +1,9 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class RemoteControlledBehavior : Behavior + { + public RemoteControlledBehavior() : base(BehaviorType.RemoteControlled) + { + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs deleted file mode 100644 index eb8305572..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureAggressiveBehavior.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Perpetuum.Zones.NpcSystem.AI -{ - public class SmartCreatureAggressiveBehavior : SmartCreatureBehavior - { - public SmartCreatureAggressiveBehavior() : base(SmartCreatureBehaviorType.Aggressive) - { - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs deleted file mode 100644 index 8a05bf260..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureBehavior.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Perpetuum.Zones.NpcSystem.AI -{ - public class SmartCreatureBehavior - { - public SmartCreatureBehaviorType Type { get; private set; } - - protected SmartCreatureBehavior(SmartCreatureBehaviorType type) - { - Type = type; - } - - public static SmartCreatureBehavior Create(SmartCreatureBehaviorType type) - { - switch (type) - { - case SmartCreatureBehaviorType.Neutral: - return new SmartCreatureNeutralBehavior(); - case SmartCreatureBehaviorType.Aggressive: - return new SmartCreatureAggressiveBehavior(); - case SmartCreatureBehaviorType.Passive: - return new SmartCreaturePassiveBehavior(); - case SmartCreatureBehaviorType.RemoteControlled: - return new SmartCreatureRemoteControlledBehavior(); - default: - return new SmartCreaturePassiveBehavior(); - } - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs deleted file mode 100644 index c997bf1f2..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureNeutralBehavior.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Perpetuum.Zones.NpcSystem.AI -{ - public class SmartCreatureNeutralBehavior : SmartCreatureBehavior - { - public SmartCreatureNeutralBehavior() : base(SmartCreatureBehaviorType.Neutral) - { - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs deleted file mode 100644 index 5f1addba4..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreaturePassiveBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Perpetuum.Zones.NpcSystem.AI -{ - public class SmartCreaturePassiveBehavior : SmartCreatureBehavior - { - public SmartCreaturePassiveBehavior() : base(SmartCreatureBehaviorType.Passive) - { - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs deleted file mode 100644 index 763cf7873..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureRemoteControlledBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Perpetuum.Zones.NpcSystem.AI -{ - public class SmartCreatureRemoteControlledBehavior : SmartCreatureBehavior - { - public SmartCreatureRemoteControlledBehavior() : base(SmartCreatureBehaviorType.RemoteControlled) - { - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs similarity index 87% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs index 488f489d4..07da38a8d 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryCombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs @@ -3,10 +3,10 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureStationaryCombatAI : SmartCreatureCombatAI + public class StationaryCombatAI : CombatAI { private readonly IntervalTimer updateFrequency = new IntervalTimer(650); - public SmartCreatureStationaryCombatAI(SmartCreature smartCreature) : base(smartCreature) { } + public StationaryCombatAI(SmartCreature smartCreature) : base(smartCreature) { } protected override PrimaryLockSelectionStrategySelector InitSelector() { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs similarity index 63% rename from src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs index c57598d26..cffd971b2 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SmartCreatureStationaryIdleAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs @@ -2,9 +2,9 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class SmartCreatureStationaryIdleAI : SmartCreatureAI + public class StationaryIdleAI : BaseAI { - public SmartCreatureStationaryIdleAI(SmartCreature smartCreature) : base(smartCreature) { } + public StationaryIdleAI(SmartCreature smartCreature) : base(smartCreature) { } public override void Enter() { @@ -25,12 +25,12 @@ protected override void ToHomeAI() { } protected override void ToAggressorAI() { - if (this.smartCreature.Behavior.Type == SmartCreatureBehaviorType.Passive) + if (this.smartCreature.Behavior.Type == BehaviorType.Passive) { return; } - this.smartCreature.AI.Push(new SmartCreatureStationaryCombatAI(this.smartCreature)); + this.smartCreature.AI.Push(new StationaryCombatAI(this.smartCreature)); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index 7469e5f9d..fa89c7f18 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -1,126 +1,119 @@ -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; 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 IEntityServices EntityService { get; set; } 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) { - var totalToSpawn = Configuration.FlockMemberCount - MembersCount; - for (var i = 0; i < totalToSpawn; 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}"); @@ -148,12 +141,22 @@ protected virtual void CreateMemberInZone() Log($"member spawned to zone:{zone.Id} EID:{npc.Eid}"); } + 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; } @@ -162,80 +165,74 @@ protected virtual Position GetHomePosition(Position spawnOrigin) return spawnOrigin; } - public event Action NpcCreated; - protected virtual void OnNpcCreated(Npc npc) { NpcCreated?.Invoke(npc); } - private NpcBehavior GetBehavior() + protected void Log(string message) { + Logger.Info($"[Flock] ({ToString()}) - {message}"); + } - if( Presence is InterzonePresence) - { - return NpcBehavior.Create(Configuration.BehaviorType); - } - else if(Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresenceExtended) - { - // This is needed to prevent the super class from matching the next condition, which overrides the behaviour type! - return NpcBehavior.Create(Configuration.BehaviorType); - } - else if (Configuration.BehaviorType == NpcBehaviorType.Aggressive && Presence is DynamicPresence) - { - // hogy ne tamadjanak be mindenkit rogton - return NpcBehavior.Create(NpcBehaviorType.Neutral); - } - - 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 InterzonePresence ip: - { - return Configuration.SpawnOrigin; - } - case DynamicPresence dp: - { - return dp.DynamicPosition; - } - case RandomPresence rp: - { - return rp.SpawnOriginForRandomPresence; - } + 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 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 dda773c7c..34b6c2217 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs @@ -1,32 +1,49 @@ -using System; using Perpetuum.EntityFramework; +using Perpetuum.Zones.NpcSystem.AI; +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 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 345f8e335..d40143f4e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -1,8 +1,9 @@ +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.Zones.NpcSystem.AI; using System; using System.Collections.Generic; using System.Linq; -using Perpetuum.Data; -using Perpetuum.EntityFramework; namespace Perpetuum.Zones.NpcSystem.Flocks { @@ -10,7 +11,7 @@ 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, NpcBossInfoBuilder bossBuilder) { @@ -21,9 +22,11 @@ public FlockConfigurationRepository(FlockConfigurationBuilder.Factory flockConfi 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"); @@ -40,11 +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.SpecialType = (NpcSpecialType) r.GetValue("npcSpecialType"); + 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; } } @@ -70,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) @@ -87,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) @@ -123,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 c1e601c69..ac94a966e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs @@ -1,26 +1,43 @@ -using System; using Perpetuum.EntityFramework; +using Perpetuum.Zones.NpcSystem.AI; +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/NPCBossInfo.cs b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs index 938c029cb..e401f9134 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -1,83 +1,17 @@ -using Perpetuum.Data; -using Perpetuum.Players; +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 Perpetuum.Zones.NpcSystem.Flocks; using System; using System.Collections.Generic; -using System.Data; using System.Linq; using System.Threading.Tasks; 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; - } - } - - /// /// Specifies the behavior of a Boss-type NPC with various settings /// @@ -92,21 +26,39 @@ public class NpcBossInfo private readonly string _aggroMsg; private readonly CustomRiftConfig _riftConfig; private bool _speak; - - public int FlockId { get; } - public TimeSpan RespawnTime { get; set; } + 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 OverrideRelations { get; } + 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) + 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; @@ -115,7 +67,7 @@ public NpcBossInfo(EventListenerService eventChannel, int id, int flockid, doubl IsLootSplit = lootSplit; _outpostEID = outpostEID; _stabilityPts = stabilityPts; - OverrideRelations = overrideRelations; + this.overrideRelations = overrideRelations; _deathMsg = customDeathMsg; _aggroMsg = customAggroMsg; _riftConfig = riftConfig; @@ -142,18 +94,18 @@ public void OnAggro(Player aggressor) HandleBossOutpostAggro(aggressor); } - // Timer to buffer excessive decrease frequency of messages from OnDamageTaken - private readonly TimeKeeper _onDamageDebounce = new TimeKeeper(TimeSpan.FromSeconds(5)); + + /// /// Handle events to dispatch when the npc boss takes damage /// - /// The npc Boss killed - /// Player damager - public void OnDamageTaken(Npc npc, Player aggressor) + /// The npc Boss killed + /// Player damager + public void OnDamageTaken(SmartCreature smartCreature, Player aggressor) { if (_onDamageDebounce.Expired) { - PublishMessage(new NpcReinforcementsMessage(npc, npc.Zone.Id)); + PublishMessage(new NpcReinforcementsMessage(smartCreature, smartCreature.Zone.Id)); _onDamageDebounce.Reset(); } } @@ -184,13 +136,51 @@ public void OnRespawn() 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)); @@ -200,22 +190,13 @@ private void AnnouceRespawn() private void AnnounceDeath() { if (!IsAnnounced) + { return; + } PublishMessage(new NpcStateMessage(FlockId, NpcState.Dead, DateTime.UtcNow)); } - /// - /// 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 HandleBossOutpostAggro(Player aggressor) { if (IsOutpostBoss) @@ -224,9 +205,6 @@ private void HandleBossOutpostAggro(Player aggressor) } } - - // Timer to buffer excessive decrease frequency of messages from OnDamageTaken - private readonly TimeKeeper _aggroDebounce = new TimeKeeper(TimeSpan.FromSeconds(5)); private void CommunicateAggression(Unit aggressor) { if (_aggroDebounce.Expired && _speak) @@ -245,22 +223,30 @@ private void CommunicateDeath(Unit aggressor) 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 participants = npc.ThreatManager.Hostiles + .Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)) + .ToList(); var builder = StabilityAffectingEvent.Builder() .WithOutpost(outpost as Outpost) - .WithOverrideRelations(OverrideRelations) + .WithOverrideRelations(overrideRelations) .WithSapDefinition(npc.Definition) .WithSapEntityID(npc.Eid) .WithPoints(StabilityPoints) .AddParticipants(participants) .WithWinnerCorp(zone.ToPlayerOrGetOwnerPlayer(killer).CorporationEid); + PublishMessage(builder.Build()); } } @@ -268,7 +254,9 @@ private void HandleBossOutpostDeath(Npc npc, Unit killer) private void SpawnPortal(Npc npc, Unit killer) { if (!HasRiftToSpawn) + { return; + } PublishMessage(new SpawnPortalMessage(npc.Zone.Id, npc.CurrentPosition, _riftConfig)); } @@ -281,30 +269,9 @@ private void SendMessage(Unit src, string msg) } } - private void PublishMessage (IEventMessage eventMessage) + private void PublishMessage(IEventMessage eventMessage) { _eventChannel.PublishMessage(eventMessage); } - - 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; - } - } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index bc8acbcbe..da36cc7b5 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -1,1099 +1,116 @@ -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 Perpetuum.Zones.Terrains; 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; namespace Perpetuum.Zones.NpcSystem { - - public enum NpcBehaviorType - { - Passive, - Neutral, - Aggressive - } - - public enum NpcSpecialType - { - Normal, - Boss - } - - public abstract class NpcAI : IState - { - protected readonly Npc npc; - - protected NpcAI(Npc npc) - { - this.npc = npc; - } - - public virtual void Enter() - { - WriteLog("enter state = " + GetType().Name); - } - - public virtual void Exit() - { - npc.StopMoving(); - WriteLog("exit state = " + GetType().Name); - } - - public abstract void Update(TimeSpan time); - - protected virtual void ToHomeAI() - { - npc.AI.Push(new HomingAI(npc)); - } - - protected virtual void ToAggressorAI() - { - if (npc.Behavior.Type == NpcBehaviorType.Passive) - return; - - npc.AI.Push(new AggressorAI(npc)); - } - - [Conditional("DEBUG")] - protected void WriteLog(string message) - { - //Logger.DebugInfo($"NpcAI: {message}"); - } - } - - public class IdleAI : NpcAI - { - private RandomMovement _movement; - - public IdleAI(Npc npc) : base(npc) { } - - public override void Enter() - { - npc.StopAllModules(); - npc.ResetLocks(); - _movement = new RandomMovement(npc.HomePosition, npc.HomeRange); - _movement.Start(npc); - base.Enter(); - } - - public override void Update(TimeSpan time) - { - if (!npc.IsInHomeRange) - { - ToHomeAI(); - return; - } - - if (npc.ThreatManager.IsThreatened) - { - ToAggressorAI(); - return; - } - - _movement?.Update(npc, time); - } - } - - public class StationaryIdleAI : NpcAI - { - public StationaryIdleAI(Npc npc) : base(npc) { } - - public override void Enter() - { - npc.StopAllModules(); - npc.ResetLocks(); - base.Enter(); - } - - public override void Update(TimeSpan time) - { - if (npc.ThreatManager.IsThreatened) - { - ToAggressorAI(); - } - } - - protected override void ToHomeAI() { } - - protected override void ToAggressorAI() - { - if (npc.Behavior.Type == NpcBehaviorType.Passive) - return; - - npc.AI.Push(new StationaryCombatAI(npc)); - } - } - - public class StationaryCombatAI : CombatAI - { - private readonly IntervalTimer _updateFrequency = new IntervalTimer(650); - public StationaryCombatAI(Npc npc) : base(npc) { } - - protected override PrimaryLockSelectionStrategySelector InitSelector() - { - return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 1) - .WithStrategy(PrimaryLockSelectionStrategy.Closest, 2) - .WithStrategy(PrimaryLockSelectionStrategy.OptimalRange, 3) - .WithStrategy(PrimaryLockSelectionStrategy.Random, 10) - .Build(); - } - - public override void Update(TimeSpan time) - { - FindHostiles(time); - base.Update(time); - } - - private void FindHostiles(TimeSpan time) - { - _updateFrequency.Update(time); - if (_updateFrequency.Passed) - { - _updateFrequency.Reset(); - npc.LookingForHostiles(); - } - } - - protected override TimeSpan SetPrimaryDwellTime() - { - return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); - } - } - - public class CombatAI : NpcAI - { - private List _moduleActivators; - protected bool _npcHasMissiles = false; - private const int UPDATE_FREQ = 1650; - private TimeSpan _hostilesUpdateFrequency = TimeSpan.FromMilliseconds(UPDATE_FREQ); - private readonly IntervalTimer _processHostilesTimer = new IntervalTimer(UPDATE_FREQ); - private readonly IntervalTimer _primarySelectTimer = new IntervalTimer(UPDATE_FREQ); - private PrimaryLockSelectionStrategySelector _stratSelector; - public CombatAI(Npc npc) : base(npc) { } - - protected virtual PrimaryLockSelectionStrategySelector InitSelector() - { - return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 9) - .WithStrategy(PrimaryLockSelectionStrategy.Random, 1) - .Build(); - } - - public override void Enter() - { - _stratSelector = InitSelector(); - _moduleActivators = npc.ActiveModules.Select(m => new ModuleActivator(m)).ToList(); - _npcHasMissiles = npc.ActiveModules.OfType().Any(); - _processHostilesTimer.Update(_hostilesUpdateFrequency); - _primarySelectTimer.Update(_hostilesUpdateFrequency); - base.Enter(); - } - - public override void Update(TimeSpan time) - { - UpdateHostiles(time); - UpdatePrimaryTarget(time); - RunModules(time); - } - - 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); - } - } - - private UnitLock[] GetValidLocks() - { - return npc.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(npc, validLocks) ?? false; - } - - private bool IsLockValidTarget(UnitLock unitLock) - { - if (unitLock == null || unitLock.State != LockState.Locked) - return false; - - var visibility = npc.GetVisibility(unitLock.Target); - if (visibility == null) - return false; - - var r = visibility.GetLineOfSight(_npcHasMissiles); - if (r != null) - { - if (r.hit && (r.blockingFlags & BlockingFlags.Plant) == 0) - return false; - } - return unitLock.Target.GetDistance(npc) < npc.MaxCombatRange; - } - - 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 (npc.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 (npc.Behavior.Type == NpcBehaviorType.Neutral) - { - if (hostile.IsExpired) - return false; - } - - var isVisible = npc.IsVisible(hostile.unit); - if (!isVisible) - return false; - - return true; - } - - private void SetLockForHostile(Hostile hostile) - { - var mostHated = GetPrimaryOrMostHatedHostile() == 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); - } - } - - protected virtual void ProcessHostiles() - { - var hostileEnumerator = npc.ThreatManager.Hostiles.GetEnumerator(); - while (hostileEnumerator.MoveNext()) - { - var hostile = hostileEnumerator.Current; - if (!IsAttackable(hostile)) - { - npc.ThreatManager.Remove(hostile); - npc.AddPseudoThreat(hostile.unit); - continue; - } - - if (!npc.IsInLockingRange(hostile.unit)) - continue; - - SetLockForHostile(hostile); - } - } - - protected bool TryMakeFreeLockSlotFor(Hostile hostile) - { - 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; - } - - protected Hostile GetPrimaryOrMostHatedHostile() - { - var primaryHostile = npc.ThreatManager.Hostiles.Where(h => h.unit == (npc.GetPrimaryLock() as UnitLock)?.Target).FirstOrDefault(); - if (primaryHostile != null) - return primaryHostile; - return npc.ThreatManager.GetMostHatedHostile(); - } - } - - public class HomingAI : CombatAI - { - private PathMovement _movement; - private readonly double _maxReturnHomeRadius; - private readonly PathFinder _pathFinder; - - public HomingAI(Npc npc) : base(npc) - { - _maxReturnHomeRadius = (npc.HomeRange * 0.4).Clamp(3, 20); - _pathFinder = new AStarFinder(Heuristic.Manhattan, npc.IsWalkable); - } - - public override void Enter() - { - var randomHome = npc.Zone.FindPassablePointInRadius(npc.HomePosition, (int)_maxReturnHomeRadius); - if (randomHome == default) - { - randomHome = npc.HomePosition; - } - _pathFinder.FindPathAsync(npc.CurrentPosition, randomHome).ContinueWith(t => - { - 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); - - if (path == null) - { - WriteLog("Safe path not found! (" + npc.CurrentPosition + " => " + npc.HomePosition + ")"); - } - } - - _movement = new PathMovement(path); - _movement.Start(npc); - npc.BossInfo?.OnDeAggro(); - }); - - base.Enter(); - } - - public override void Update(TimeSpan time) - { - if (_movement != null) - { - _movement.Update(npc, time); - - if (_movement.Arrived) - { - npc.AI.Pop(); - return; - } - } - - base.Update(time); - } - - protected override void ToHomeAI() { } - protected override void ToAggressorAI() { } - } - - public class AggressorAI : CombatAI - { - public AggressorAI(Npc npc) : base(npc) { } - - protected override void ToAggressorAI() { } - - public override void Exit() - { - _source?.Cancel(); - - base.Exit(); - } - - public override void Update(TimeSpan time) - { - if (!npc.IsInHomeRange) - { - npc.AI.Push(new HomingAI(npc)); - return; - } - - if (!npc.ThreatManager.IsThreatened) - { - EnterEvadeMode(); - return; - } - - UpdateHostile(time); - - base.Update(time); - } - - private void EnterEvadeMode() - { - npc.AI.Pop(); - npc.AI.Push(new HomingAI(npc)); - WriteLog("Enter evade mode."); - } - - private Position _lastTargetPosition; - private PathMovement _movement; - private PathMovement _nextMovement; - - private void UpdateHostile(TimeSpan time) - { - var mostHated = GetPrimaryOrMostHatedHostile(); - if (mostHated == null) - return; - - if (!mostHated.unit.CurrentPosition.IsEqual2D(_lastTargetPosition)) - { - _lastTargetPosition = mostHated.unit.CurrentPosition; - - var findNewTargetPosition = false; - - if (!npc.IsInRangeOf3D(mostHated.unit, npc.BestCombatRange)) - { - findNewTargetPosition = true; - } - else - { - var visibility = npc.GetVisibility(mostHated.unit); - if (visibility != null) - { - var r = visibility.GetLineOfSight(_npcHasMissiles); - if (r.hit) - findNewTargetPosition = true; - } - } - - if (findNewTargetPosition) - { - FindNewAttackPositionAsync(mostHated.unit).ContinueWith(t => - { - if (t.IsCanceled) - return; - - var path = t.Result; - if (path == null) - { - npc.ThreatManager.Remove(mostHated); - npc.AddPseudoThreat(mostHated.unit); - return; - } - - Interlocked.Exchange(ref _nextMovement, new PathMovement(path)); - }); - } - } - - if (_nextMovement != null) - { - _movement = Interlocked.Exchange(ref _nextMovement, null); - _movement.Start(npc); - } - - _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) - { - 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); - - Node current; - while (pq.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 (!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 - }; - - pq.Enqueue(newNode); - } - } - - 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(); - - var node = current; - while (node != null) - { - stack.Push(node.position); - node = node.parent; - } - - return stack.ToList(); - } - - private 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(); - } - } - } - - public class NpcBehavior - { - public NpcBehaviorType Type { get; private set; } - - protected NpcBehavior(NpcBehaviorType type) - { - Type = type; - } - - 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(); - } - } - } - - 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 + public class Npc : SmartCreature, ITaggable { - private readonly TagHelper _tagHelper; - private const double CALL_FOR_HELP_ARMOR_THRESHOLD = 0.2; - private readonly ThreatManager _threatManager; - private Lazy _maxCombatRange; - private Lazy _optimalCombatRange; - private TimeSpan _lastHelpCalled; - private readonly IPseudoThreatManager _pseudoThreatManager; + private readonly TagHelper tagHelper; public Npc(TagHelper tagHelper) { - _maxCombatRange = new Lazy(CalculateMaxCombatRange); - _optimalCombatRange = new Lazy(CalculateCombatRange); - _tagHelper = tagHelper; - _threatManager = new ThreatManager(); - AI = new StackFSM(); - _pseudoThreatManager = new PseudoThreatManager(); - } - - public NpcBehavior Behavior { get; set; } - public NpcSpecialType SpecialType { get; set; } - public NpcBossInfo BossInfo { get; set; } - public int EP { get; private 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 _optimalCombatRange.Value; } - } - - public int MaxCombatRange - { - get { return _maxCombatRange.Value; } - } - - public bool IsStationary - { - get { return MaxSpeed.IsZero(); } - } - - 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) - { - if (hostile.IsPlayer()) - { - BossInfo?.OnAggro(hostile as Player); - } - _threatManager.GetOrAddHostile(hostile).AddThreat(threat); - - RemovePseudoThreat(hostile); - - 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 void AddPseudoThreat(Unit hostile) - { - _pseudoThreatManager.AddOrRefreshExisting(hostile); - } - - private readonly IntervalTimer _psuedoUpdateFreq = new IntervalTimer(TimeSpan.FromMilliseconds(650)); - private void UpdatePseudoThreats(TimeSpan time) - { - _psuedoUpdateFreq.Update(time); - if (_psuedoUpdateFreq.Passed) - { - _pseudoThreatManager.Update(_psuedoUpdateFreq.Elapsed); - _psuedoUpdateFreq.Reset(); - } - } - - private void RemovePseudoThreat(Unit hostile) - { - _pseudoThreatManager.Remove(hostile); - } - - 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: - { - _optimalCombatRange = new Lazy(CalculateCombatRange); - _maxCombatRange = new Lazy(CalculateMaxCombatRange); - 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; - - BossInfo?.OnDamageTaken(this, player); - 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"); - - BossInfo?.OnDeath(this, killer); - 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)); + this.tagHelper = tagHelper; } - 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()) - { - - if (BossInfo?.IsLootSplit ?? false) - { - //Boss - Split loot equally to all participants - 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 - { - //Normal case: loot can awarded in full to tagger - 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); - } - - if (EP > 0) - { - var awardedPlayers = new List(); - foreach (var hostile in ThreatManager.Hostiles.Where(x => x.unit is Player)) - { - var playerUnit = hostile.unit; - var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); - hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, EP); - awardedPlayers.Add(playerUnit); - } - - _pseudoThreatManager.AwardPseudoThreats(awardedPlayers, zone, EP); - } + public NpcSpecialType SpecialType { get; set; } - scope.Complete(); - } - } + public int EP { get; private set; } + public ILootGenerator LootGenerator { get; set; } - /// - /// 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) + public void Tag(Player tagger, TimeSpan duration) { - var missionGuid = GetMissionGuid(); - var missionOwner = MissionHelper.FindMissionOwnerByGuid(missionGuid); + tagHelper.DoTagging(this, tagger, duration); + } - var missionOwnerPlayer = zone.GetPlayer(missionOwner); - if (missionOwnerPlayer == null) + [CanBeNull] + public Player GetTagger() + { + return TagHelper.GetTagger(this); + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) { - //the owner is not this zone - //address the mission plugin directly + base.AcceptVisitor(visitor); + } + } - 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} - }; + public override IDictionary GetDebugInfo() + { + var info = base.GetDebugInfo(); + var homeDistance = HomePosition.TotalDistance2D(CurrentPosition); - if (killerUnit is Player killerPlayer && killerPlayer.Character.Id != missionOwner.Id) - { - info[k.assistingCharacterID] = killerPlayer.Character.Id; - } + 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); - Task.Run(() => - { - MissionHelper.MissionProcessor.NpcGotKilledInAway(missionOwner, missionGuid, info); - }); - return; + var currentAI = AI.Current; + + if (currentAI != null) + { + info.Add("fsm", currentAI.GetType().Name); } - //local enqueue, this is the proper player, we can skip gang - EnqueueKill(missionOwnerPlayer, killerUnit); + info.Add("threat", ThreatManager.ToDebugString()); + Group?.AddDebugInfoToDictionary(info); + info.Add("ismission", GetMissionGuid() != Guid.Empty); + + return info; } - private void EnqueueKill(Player missionOwnerPlayer, Unit killerUnit) + protected override void OnDead(Unit killer) { - var eventSourcePlayer = missionOwnerPlayer; - var killerPlayer = killerUnit as Player; - if (killerPlayer != null && !killerPlayer.Equals(missionOwnerPlayer)) - { - eventSourcePlayer = killerPlayer; - } + var zone = Zone; + var tagger = GetTagger(); - Logger.DebugInfo($" >>>> EventSource: botName:{eventSourcePlayer.Name} o:{eventSourcePlayer.Owner} characterId:{eventSourcePlayer.Character.Id} MissionOwner: botName:{missionOwnerPlayer.Name} o:{missionOwnerPlayer.Owner} characterId:{missionOwnerPlayer.Character.Id}"); + Debug.Assert(zone != null, "zone != null"); - //local enqueue, this is the proper player, we can skip gang - missionOwnerPlayer.MissionHandler.EnqueueMissionEventInfoLocally(new KillEventInfo(eventSourcePlayer, this, CurrentPosition)); + BossInfo?.OnDeath(this, killer); + HandleNpcDeadAsync(zone, killer, tagger) + .ContinueWith((t) => base.OnDead(killer)) + .LogExceptions(); } - protected override void OnTileChanged() + public override bool IsWalkable(Vector2 position) { - base.OnTileChanged(); - LookingForHostiles(); + return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); } - public void LookingForHostiles() + public override bool IsWalkable(Position position) { - foreach (var visibility in GetVisibleUnits()) - { - AddBodyPullThreat(visibility.Target); - } + return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); } - protected override void OnUpdate(TimeSpan time) + public override bool IsWalkable(int x, int y) { - base.OnUpdate(time); - - AI.Update(time); - - UpdatePseudoThreats(time); + return Zone.IsWalkableForNpc(x, y, Slope); } protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { SetEP(zone); - States.Aggressive = Behavior.Type == NpcBehaviorType.Aggressive; base.OnEnterZone(zone, enterType); - - if (IsStationary) - { - AI.Push(new StationaryIdleAI(this)); - } - else - { - AI.Push(new IdleAI(this)); - } } private void SetEP(IZone zone) @@ -1117,59 +134,23 @@ public override string InfoString { get { - var s = $"Npc:{ED.Name}:{Eid}"; + var infoString = $"Npc:{ED.Name}:{Eid}"; var zone = Zone; if (zone != null) { - s += " z:" + zone.Id; + infoString += " z:" + zone.Id; } - if (_group != null) - s += " g:" + _group.Name; + if (Group != null) + { + infoString += " g:" + Group.Name; + } - return s; + return infoString; } } - private const double AGGRO_RANGE = 30; - - private bool IsInAggroRange(Unit target) - { - return IsStationary || IsInRangeOf3D(target, AGGRO_RANGE); - } - - private readonly TimeKeeper _debounceLockChange = new TimeKeeper(TimeSpan.FromSeconds(2.5)); - protected override void OnUnitLockStateChanged(Lock @lock) - { - if (!_debounceLockChange.Expired) - return; - - 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); - _debounceLockChange.Reset(); - } - - private readonly TimeKeeper _debounceBodyPull = new TimeKeeper(TimeSpan.FromSeconds(2.5)); - protected override void OnUnitTileChanged(Unit target) - { - if (!_debounceBodyPull.Expired) - return; - - AddBodyPullThreat(target); - _debounceBodyPull.Reset(); - } - internal override bool IsHostile(Player player) { return true; @@ -1190,25 +171,6 @@ protected override bool IsHostileFor(Unit unit) return unit.IsHostile(this); } - /// - /// 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) - { - if (_threatManager.Contains(target)) - return true; - - if (Behavior.Type == NpcBehaviorType.Passive) - return false; - - return threat.type != ThreatType.Undefined; - } - protected override void UpdateUnitVisibility(Unit target) { if (target is SentryTurret) @@ -1217,150 +179,130 @@ protected override void UpdateUnitVisibility(Unit target) } } - private void AddBodyPullThreat(Unit enemy) + private Task HandleNpcDeadAsync(IZone zone, Unit killer, Player tagger) { - if (!IsHostile(enemy)) - return; - - var helper = new BodyPullThreatHelper(this); - enemy.AcceptVisitor(helper); + return Task.Run(() => HandleNpcDead(zone, killer, tagger)); } - public bool CallForHelp { private get; set; } - - private void CallingForHelp() + private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) { - if (!CallForHelp) - return; - - if (!GlobalTimer.IsPassed(ref _lastHelpCalled, TimeSpan.FromSeconds(5))) - return; - - var group = _group; - if (group == null) - return; + Logger.DebugInfo($" >>>> NPC died. Killer unitName:{killer.Name} o:{killer.Owner} Tagger botname:{tagger?.Name} o:{killer.Owner} characterId:{tagger?.Character.Id}"); - foreach (var member in group.Members.Where(flockMember => flockMember != this)) + using (var scope = Db.CreateTransaction()) { - member.HelpingFor(this); - } - } - - private void HelpingFor(Npc caller) - { - if (Armor.Ratio(ArmorMax) < CALL_FOR_HELP_ARMOR_THRESHOLD) - return; + if (BossInfo?.IsLootSplit ?? false) + { + List participants = new List(); + participants = ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)).ToList(); - _threatManager.Clear(); - foreach (var hostile in caller.ThreatManager.Hostiles) - { - AddThreat(hostile.unit, new Threat(ThreatType.Undefined, hostile.Threat), true); - } - } + if (participants.Count > 0) + { + ISplittableLootGenerator splitLooter = new SplittableLootGenerator(LootGenerator); + List lootGenerators = splitLooter.GetGenerators(participants.Count); - public void AddAssistThreat(Unit assistant, Unit target, Threat threat) - { - if (!_threatManager.Contains(target)) - return; + 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); + } - if (!CanAddThreatTo(assistant, threat)) - return; + var killerPlayer = zone.ToPlayerOrGetOwnerPlayer(killer); - AddThreat(assistant, threat, true); - } + if (GetMissionGuid() != Guid.Empty) + { + Logger.DebugInfo(" >>>> NPC is mission related."); - private int CalculateCombatRange() - { - double range = (int)ActiveModules.Where(m => m.IsRanged) - .Select(module => module.OptimalRange) - .Concat(new[] { MaxTargetingRange }) - .Min(); + SearchForMissionOwnerAndSubmitKill(zone, killer); + } + else + { + Logger.DebugInfo(" >>>> independent NPC."); - range *= BEST_COMBAT_RANGE_MODIFIER; - range = Math.Max(3, range); - return (int)range; - } + if (killerPlayer != null) + { + EnqueueKill(killerPlayer, killer); + } + } - private int CalculateMaxCombatRange() - { - double range = ActiveModules.Where(m => m.IsRanged) - .Select(module => (int)(module.OptimalRange + module.Falloff)) - .Max(); + if (EP > 0) + { + var awardedPlayers = new List(); - range = Math.Max(3, range); - return (int)range; - } + foreach (var hostile in ThreatManager.Hostiles.Where(x => x.unit is Player)) + { + var playerUnit = hostile.unit; + var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); - private const double BEST_COMBAT_RANGE_MODIFIER = 0.9; + hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, EP); + awardedPlayers.Add(playerUnit); + } - private class BodyPullThreatHelper : IEntityVisitor, IEntityVisitor - { - private readonly Npc _npc; + PseudoThreatManager.AwardPseudoThreats(awardedPlayers, zone, EP); + } - public BodyPullThreatHelper(Npc npc) - { - _npc = npc; + scope.Complete(); } + } - public void Visit(Player player) - { - if (_npc.Behavior.Type != NpcBehaviorType.Aggressive) - return; - - if (player.HasTeleportSicknessEffect) - return; - - if (_npc.ThreatManager.Hostiles.Any(h => h.unit.Eid == player.Eid)) - return; - - if (!_npc.IsInAggroRange(player)) - return; - - var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); - _npc.AddThreat(player, new Threat(ThreatType.Bodypull, threat)); - } + /// + /// 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); - public void Visit(AreaBomb bomb) + if (missionOwnerPlayer == null) { - 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 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}, + }; - var threat = Threat.BODY_PULL; - if (_npc.ThreatManager.IsThreatened) + if (killerUnit is Player killerPlayer && killerPlayer.Character.Id != missionOwner.Id) { - var h = _npc.ThreatManager.GetMostHatedHostile(); - if (h != null) - threat = h.Threat * 100; + info[k.assistingCharacterID] = killerPlayer.Character.Id; } - _npc.AddThreat(bomb, new Threat(ThreatType.Bodypull, threat + FastRandom.NextDouble(0, 5))); + Task.Run(() => + { + MissionHelper.MissionProcessor.NpcGotKilledInAway(missionOwner, missionGuid, info); + }); + + return; } - } - public override bool IsWalkable(Vector2 position) - { - return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); + EnqueueKill(missionOwnerPlayer, killerUnit); } - public override bool IsWalkable(Position position) + private void EnqueueKill(Player missionOwnerPlayer, Unit killerUnit) { - return Zone.IsWalkableForNpc((int)position.X, (int)position.Y, Slope); - } + var eventSourcePlayer = missionOwnerPlayer; - public override bool IsWalkable(int x, int y) - { - return Zone.IsWalkableForNpc(x, y, Slope); - } + if (killerUnit is Player killerPlayer && !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}"); + + missionOwnerPlayer.MissionHandler.EnqueueMissionEventInfoLocally(new KillEventInfo(eventSourcePlayer, this, CurrentPosition)); + } } } \ 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/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/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 index 6acf6ae6b..aa58b3e9e 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/PathFinders/RoamingStates.cs @@ -203,47 +203,4 @@ public virtual void Update(TimeSpan time) IsDeadAndExiting(members); } } - - public class RoamingState : NullRoamingState - { - public RoamingState(IRoamingPresence presence) : base(presence) { } - - private bool IsAllNotIdle(Npc[] members) - { - var idleMembersCount = members.Select(m => m.AI.Current).OfType().Count(); - return idleMembersCount < members.Length; - } - - public override void Update(TimeSpan time) - { - if (IsRunningTask) - return; - - var members = GetAllMembers(); - if (IsDeadAndExiting(members)) - return; - - if (IsAllNotIdle(members)) - return; - - RunTask(() => FindNextRoamingPosition(), t => { }); - } - - 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/Presence.cs b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs index a390d17cc..6bd1996ac 100644 --- a/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs +++ b/src/Perpetuum/Zones/NpcSystem/Presences/Presence.cs @@ -1,17 +1,19 @@ +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 { - public int ID { get; private set; } private ImmutableHashSet _flocks = ImmutableHashSet.Empty; + private readonly IntervalTimer _updateTimer = new IntervalTimer(TimeSpan.FromSeconds(2)); + + public int ID { get; private set; } public IPresenceConfiguration Configuration { get; private set; } @@ -22,35 +24,22 @@ public class Presence : INpcGroup public IEnumerable Flocks => _flocks; - public Presence(IZone zone, IPresenceConfiguration configuration) - { - Zone = zone; - Configuration = configuration; - ID = Configuration.ID; - } - 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) @@ -59,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) + { return; + } OnUpdate(_updateTimer.Elapsed); @@ -96,8 +67,6 @@ public void Update(TimeSpan time) _updateTimer.Reset(); } - protected virtual void OnUpdate(TimeSpan time) { } - public IDictionary ToDictionary(bool withFlock = false) { var result = new Dictionary @@ -117,37 +86,21 @@ public IDictionary ToDictionary(bool withFlock = false) 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; } @@ -156,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); } @@ -169,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); + } } - public virtual void Log(string message) + protected virtual void OnFlockAdded(Flock flock) { - Logger.Info($"[Presence] ({ToString()}) - {message}"); + Log($"Flock added. {flock.Configuration.Name}"); + flock.NpcCreated += OnFlockNpcCreated; + } + + protected virtual void OnUpdate(TimeSpan time) + { + } + + 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/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 57624afe9..663a2ea6d 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -1,4 +1,6 @@ using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; using Perpetuum.StateMachines; using Perpetuum.Timers; using Perpetuum.Units; @@ -22,30 +24,37 @@ public class SmartCreature : Creature { private const double AggroRange = 30; private const double BestComnatRangeModifier = 0.9; + private const double CallForHelpArmorThreshold = 0.2; private readonly ThreatManager threatManager; - private readonly IPseudoThreatManager pseudoThreatManager; - private readonly Lazy maxCombatRange; - private readonly Lazy optimalCombatRange; + private readonly PseudoThreatManager pseudoThreatManager; 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 maxCombatRange; + private Lazy optimalCombatRange; + private TimeSpan _lastHelpCalled; [CanBeNull] - private INpcGroup group; + private ISmartCreatureGroup group; public StackFSM AI { get; private set; } - public SmartCreatureBehavior Behavior { get; set; } + public Behavior Behavior { get; set; } public double HomeRange { get; set; } public Position HomePosition { get; set; } - public IThreatManager ThreatManager + public ThreatManager ThreatManager { get { return this.threatManager; } } + public PseudoThreatManager PseudoThreatManager + { + get { return pseudoThreatManager; } + } + public int BestCombatRange { get { return this.optimalCombatRange.Value; } @@ -61,7 +70,7 @@ public bool IsInHomeRange get { return CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); } } - public INpcGroup Group + public ISmartCreatureGroup Group { get { return group; } } @@ -71,10 +80,14 @@ public virtual bool IsStationary get { return MaxSpeed.IsZero(); } } + public bool CallForHelp { private get; set; } + + public NpcBossInfo BossInfo { get; set; } + public SmartCreature() { - maxCombatRange = new Lazy(CalculateMaxCombatRange); - optimalCombatRange = new Lazy(CalculateCombatRange); + RecalculateMaxCombatRange(); + RecalculateOptimalCombatRange(); threatManager = new ThreatManager(); AI = new StackFSM(); pseudoThreatManager = new PseudoThreatManager(); @@ -93,11 +106,48 @@ public void AddPseudoThreat(Unit hostile) pseudoThreatManager.AddOrRefreshExisting(hostile); } - public void SetGroup(INpcGroup group) + public void SetGroup(ISmartCreatureGroup group) { this.group = group; } + public void RecalculateOptimalCombatRange() + { + optimalCombatRange = new Lazy(CalculateCombatRange); + } + + public void RecalculateMaxCombatRange() + { + maxCombatRange = 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: + { + var percentage = Armor.Ratio(ArmorMax); + + if (percentage <= CallForHelpArmorThreshold) + { + CallingForHelp(); + } + + break; + } + } + } + protected override void OnUnitLockStateChanged(Lock @lock) { if (!debounceLockChange.Expired) @@ -148,7 +198,7 @@ private void AddBodyPullThreat(Unit enemy) return; } - var helper = new SmartCreatureBodyPullThreatHelper(this); + var helper = new BodyPullThreatHelper(this); enemy.AcceptVisitor(helper); } @@ -233,7 +283,7 @@ public bool CanAddThreatTo(Unit target, Threat threat) return true; } - if (Behavior.Type == SmartCreatureBehaviorType.Passive) + if (Behavior.Type == BehaviorType.Passive) { return false; } @@ -268,17 +318,17 @@ protected override void OnTileChanged() protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { - States.Aggressive = Behavior.Type == SmartCreatureBehaviorType.Aggressive; + States.Aggressive = Behavior.Type == BehaviorType.Aggressive; base.OnEnterZone(zone, enterType); if (IsStationary) { - AI.Push(new SmartCreatureStationaryIdleAI(this)); + AI.Push(new StationaryIdleAI(this)); } else { - AI.Push(new SmartCreatureIdleAI(this)); + AI.Push(new IdleAI(this)); } } @@ -292,7 +342,7 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) return; } - //BossInfo?.OnDamageTaken(this, player); + BossInfo?.OnDamageTaken(this, player); AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); } @@ -320,5 +370,40 @@ private void UpdatePseudoThreats(TimeSpan time) pseudoUpdateFreq.Reset(); } } + + 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(SmartCreature caller) + { + if (Armor.Ratio(ArmorMax) < CallForHelpArmorThreshold) + { + return; + } + + ThreatManager.Clear(); + + foreach (var hostile in caller.ThreatManager.Hostiles) + { + AddThreat(hostile.unit, new Threat(ThreatType.Undefined, hostile.Threat), true); + } + } } } diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs index f7922779e..9b9fe7e65 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs @@ -1,11 +1,11 @@ -using System; +using Perpetuum.Comparers; +using Perpetuum.Timers; +using Perpetuum.Units; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; -using Perpetuum.Comparers; -using Perpetuum.Timers; -using Perpetuum.Units; namespace Perpetuum.Zones.NpcSystem { @@ -50,7 +50,7 @@ public override string ToString() public static Threat Multiply(Threat threat, double multiplier) { - return new Threat(threat.type,threat.value * multiplier); + return new Threat(threat.type, threat.value * multiplier); } } @@ -74,7 +74,7 @@ public Hostile(Unit unit) public void AddThreat(Threat threat) { - if ( threat.value <= 0.0 ) + if (threat.value <= 0.0) return; Threat += threat.value; @@ -121,9 +121,14 @@ public int CompareTo(Hostile other) public interface IThreatManager { bool IsThreatened { get; } + bool Contains(Unit hostile); + void Remove(Hostile hostile); + ImmutableSortedSet Hostiles { get; } + + void Clear(); } public static class ThreatExtensions @@ -137,7 +142,7 @@ public static Hostile GetMostHatedHostile(this IThreatManager manager) public class ThreatManager : IThreatManager { - private ImmutableDictionary _hostiles = ImmutableDictionary.Empty; + private ImmutableDictionary _hostiles = ImmutableDictionary.Empty; public Hostile GetOrAddHostile(Unit unit) { @@ -173,9 +178,9 @@ public void Remove(Hostile hostile) ImmutableInterlocked.TryRemove(ref _hostiles, hostile.unit.Eid, out hostile); } - public string ToDebugString() + public string ToDebugString() { - if ( _hostiles.Count == 0 ) + if (_hostiles.Count == 0) return string.Empty; var sb = new StringBuilder(); @@ -186,7 +191,7 @@ public string ToDebugString() 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.AppendFormat(" {0} ({1}) => {2}", hostile.unit.ED.Name, hostile.unit.Eid, hostile.Threat); sb.AppendLine(); } @@ -253,7 +258,7 @@ public void AddOrRefreshExisting(Unit hostile) public void Remove(Unit hostile) { - lock(_lock) + lock (_lock) _pseudoThreats.RemoveAll(x => x.Unit == hostile); } diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 645f7b0e2..e548a8bdd 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -31,7 +31,7 @@ public override void Initialize() { rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); AddProperty(rcBandwidthUsage); - Behavior = SmartCreatureBehavior.Create(SmartCreatureBehaviorType.RemoteControlled); + Behavior = Behavior.Create(BehaviorType.RemoteControlled); base.Initialize(); } From fdb013406cccb21c129ebfe44a860c4284efc4c9 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Thu, 2 Nov 2023 23:19:14 +0500 Subject: [PATCH 405/457] Refactoring AI part 3 --- src/Perpetuum/Units/UnitExtensions.cs | 5 -- .../PrimaryLockSelectionStrategySelector.cs | 7 -- .../Zones/NpcSystem/PrimaryTargetStrategy.cs | 83 ++++--------------- .../Zones/NpcSystem/SmartCreature.cs | 10 +-- 4 files changed, 20 insertions(+), 85 deletions(-) diff --git a/src/Perpetuum/Units/UnitExtensions.cs b/src/Perpetuum/Units/UnitExtensions.cs index f4b7092c5..48f0a8811 100644 --- a/src/Perpetuum/Units/UnitExtensions.cs +++ b/src/Perpetuum/Units/UnitExtensions.cs @@ -183,11 +183,6 @@ public static void SpreadAssistThreatToNpcs(this Unit unit, Unit assistant, Thre return; } - foreach (var npc in unit.GetWitnessUnits()) - { - npc.AddAssistThreat(assistant, unit, threat); - } - foreach (var smartCreature in unit.GetWitnessUnits()) { smartCreature.AddAssistThreat(assistant, unit, threat); diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs index cf02aed6f..dd61335e1 100644 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs @@ -12,13 +12,6 @@ public PrimaryLockSelectionStrategySelector(WeightedCollection All = new Dictionary() + public static Dictionary All = + new Dictionary() { { PrimaryLockSelectionStrategy.Random, TargetRandom }, { PrimaryLockSelectionStrategy.Hostile, TargetMostHated }, @@ -17,36 +17,14 @@ public static class Strategies { PrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal } }; - public static Dictionary SmartCreatureAll = - new Dictionary() - { - { PrimaryLockSelectionStrategy.Random, SmartCreatureTargetRandom }, - { PrimaryLockSelectionStrategy.Hostile, SmartCreatureTargetMostHated }, - { PrimaryLockSelectionStrategy.Closest, SmartCreatureTargetClosest }, - { PrimaryLockSelectionStrategy.OptimalRange, SmartCreatureTargetWithinOptimal } - }; - public static TargetSelectionStrategy GetStrategy(PrimaryLockSelectionStrategy strategyType) { return All.GetOrDefault(strategyType); } - public static SmartCreatureTargetSelectionStrategy SmartCreatureGetStrategy(PrimaryLockSelectionStrategy strategyType) - { - return SmartCreatureAll.GetOrDefault(strategyType); - } - - public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, Npc npc, UnitLock[] locks) - { - var strat = GetStrategy(strategyType); - if (strat == null) - return false; - return strat(npc, locks); - } - public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, SmartCreature smartCreature, UnitLock[] locks) { - var primaryLockSelectionStrategy = SmartCreatureGetStrategy(strategyType); + var primaryLockSelectionStrategy = GetStrategy(strategyType); if (primaryLockSelectionStrategy == null) { @@ -56,15 +34,7 @@ public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, return primaryLockSelectionStrategy(smartCreature, locks); } - private static bool TargetMostHated(Npc npc, UnitLock[] locks) - { - var hostiles = npc.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(npc, mostHostileLock); - } - - private static bool SmartCreatureTargetMostHated(SmartCreature smartCreature, UnitLock[] locks) + 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)); @@ -72,52 +42,35 @@ private static bool SmartCreatureTargetMostHated(SmartCreature smartCreature, Un .OrderByDescending(u => hostiles.Where(h => h.unit.Eid == u.Target.Eid).FirstOrDefault()?.Threat ?? 0) .FirstOrDefault(); - return SmartCreatureTrySetPrimaryLock(smartCreature, mostHostileLock); + return TrySetPrimaryLock(smartCreature, mostHostileLock); } - private static bool TargetWithinOptimal(Npc npc, UnitLock[] locks) + private static bool TargetWithinOptimal(SmartCreature smartCreature, UnitLock[] locks) { - return TrySetPrimaryLock(npc, locks.Where(k => k.Target.GetDistance(npc) < npc.BestCombatRange).RandomElement()); + return TrySetPrimaryLock( + smartCreature, + locks + .Where(k => k.Target.GetDistance(smartCreature) < smartCreature.BestCombatRange) + .RandomElement()); } - private static bool SmartCreatureTargetWithinOptimal(SmartCreature smartCreature, UnitLock[] locks) + private static bool TargetClosest(SmartCreature smartCreature, UnitLock[] locks) { - return SmartCreatureTrySetPrimaryLock(smartCreature, locks.Where(k => k.Target.GetDistance(smartCreature) < smartCreature.BestCombatRange).RandomElement()); + return TrySetPrimaryLock(smartCreature, locks.OrderBy(u => u.Target.GetDistance(smartCreature)).First()); } - private static bool TargetClosest(Npc npc, UnitLock[] locks) + private static bool TargetRandom(SmartCreature smartCreature, UnitLock[] locks) { - return TrySetPrimaryLock(npc, locks.OrderBy(u => u.Target.GetDistance(npc)).First()); + return TrySetPrimaryLock(smartCreature, locks.RandomElement()); } - private static bool SmartCreatureTargetClosest(SmartCreature smartCreature, UnitLock[] locks) - { - return SmartCreatureTrySetPrimaryLock(smartCreature, locks.OrderBy(u => u.Target.GetDistance(smartCreature)).First()); - } - - private static bool TargetRandom(Npc npc, UnitLock[] locks) - { - return TrySetPrimaryLock(npc, locks.RandomElement()); - } - - private static bool SmartCreatureTargetRandom(SmartCreature smartCreature, UnitLock[] locks) - { - return SmartCreatureTrySetPrimaryLock(smartCreature, locks.RandomElement()); - } - - private static bool TrySetPrimaryLock(Npc npc, Lock l) - { - if (l == null) return false; - npc.SetPrimaryLock(l); - return true; - } - - private static bool SmartCreatureTrySetPrimaryLock(SmartCreature smartCreature, Lock targetLock) + 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/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 663a2ea6d..326ea4919 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -14,12 +14,6 @@ namespace Perpetuum.Zones.NpcSystem { - #region move to separate files - - - - #endregion - public class SmartCreature : Creature { private const double AggroRange = 30; @@ -32,7 +26,7 @@ public class SmartCreature : Creature private readonly IntervalTimer pseudoUpdateFreq = new IntervalTimer(TimeSpan.FromMilliseconds(650)); private Lazy maxCombatRange; private Lazy optimalCombatRange; - private TimeSpan _lastHelpCalled; + private TimeSpan lastHelpCalled; [CanBeNull] private ISmartCreatureGroup group; @@ -378,7 +372,7 @@ private void CallingForHelp() return; } - if (!GlobalTimer.IsPassed(ref _lastHelpCalled, TimeSpan.FromSeconds(5))) + if (!GlobalTimer.IsPassed(ref lastHelpCalled, TimeSpan.FromSeconds(5))) return; var group = Group; From f32ff5f390c5e5a466c6679856ccc121bd1ebd72 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sun, 5 Nov 2023 18:18:48 +0500 Subject: [PATCH 406/457] Standings --- src/Perpetuum.ExportedTypes/EffectCategory.cs | 99 +- src/Perpetuum.ExportedTypes/EffectType.cs | 197 +-- src/Perpetuum/Modules/Module.cs | 16 +- src/Perpetuum/Modules/RemoteController.cs | 36 +- src/Perpetuum/Perpetuum.csproj | 4 + src/Perpetuum/Players/ErrorPacketBuilder.cs | 20 + src/Perpetuum/Players/Player.cs | 1496 +++++++++-------- src/Perpetuum/Players/PlayerMovement.cs | 63 + src/Perpetuum/Units/Unit.cs | 114 +- .../NpcSystem/AI/BodyPullThreatHelper.cs | 4 +- .../NpcSystem/AI/SentryTurretCombatAI.cs | 21 + .../Zones/NpcSystem/AI/SentryTurretIdleAI.cs | 17 + .../Zones/NpcSystem/AI/StationaryCombatAI.cs | 3 +- .../NpcSystem/PrimaryLockSelectionStrategy.cs | 1 + .../Zones/NpcSystem/PrimaryTargetStrategy.cs | 8 +- .../Zones/NpcSystem/SmartCreature.cs | 21 +- .../Zones/RemoteControl/BandwidthHandler.cs | 35 +- .../Zones/RemoteControl/SentryTurret.cs | 31 +- 18 files changed, 1209 insertions(+), 977 deletions(-) create mode 100644 src/Perpetuum/Players/ErrorPacketBuilder.cs create mode 100644 src/Perpetuum/Players/PlayerMovement.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 9e7b6d1b3..9a2853981 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -4,56 +4,57 @@ 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: New Zone effects + effcat_mine_detection = 0x0000400000000000, // OPP: Mine detection + effcat_remote_control = 0x0000800000000000, // OPP: Remote control } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 9316d5787..f0b1e3cf0 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -5,103 +5,103 @@ 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, @@ -121,6 +121,7 @@ public enum EffectType effect_gamma_t2 = 118, effect_gamma_t3 = 119, effect_gamma_tc = 120, - effect_mine_detector = 121, + effect_mine_detector = 121, + effect_remote_control = 122, } } diff --git a/src/Perpetuum/Modules/Module.cs b/src/Perpetuum/Modules/Module.cs index 3391793fb..2ab0d74b0 100644 --- a/src/Perpetuum/Modules/Module.cs +++ b/src/Perpetuum/Modules/Module.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using System.Linq; -using Perpetuum.Containers; +using Perpetuum.Containers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Modules { @@ -19,11 +19,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 +63,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 +85,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 +109,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(); diff --git a/src/Perpetuum/Modules/RemoteController.cs b/src/Perpetuum/Modules/RemoteController.cs index fd2c6a8a3..6e8a1a7fa 100644 --- a/src/Perpetuum/Modules/RemoteController.cs +++ b/src/Perpetuum/Modules/RemoteController.cs @@ -1,14 +1,15 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Units; +using Perpetuum.Zones; using Perpetuum.Zones.Beams; +using Perpetuum.Zones.Effects; +using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.RemoteControl; -using Perpetuum.Zones; using System; using System.Linq; -using Perpetuum.Zones.Finders; -using Perpetuum.Units; namespace Perpetuum.Modules { @@ -18,6 +19,7 @@ public class RemoteControllerModule : ActiveModule private const double SentryTurretDeployRange = 2; private readonly ModuleProperty bandwidthMax; private BandwidthHandler bandwidthHandler; + protected readonly EffectToken effectToken = EffectToken.NewToken(); public double BandwidthMax { @@ -44,7 +46,7 @@ private void InitBandwidthHandler(RemoteControllerModule module) public void SyncRemoteChannels() { - this.bandwidthHandler.Update(); + bandwidthHandler.Update(); } [CanBeNull] @@ -68,6 +70,7 @@ public void UseRemoteChannel(SentryTurret turret) { bandwidthHandler.UseRemoteChannel(turret); turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; + bandwidthHandler.Update(); } public void UseRemoteChannel(RemoteChannel newChannel) @@ -119,8 +122,12 @@ protected override void OnAction() } Position targetPosition = lockPosition.Value; - zone.Units.OfType().WithinRange(targetPosition, SentryTurretDeployRange).Any().ThrowIfTrue(ErrorCodes.BlobEmitterInRange); - + zone.Units + .OfType() + .WithinRange(targetPosition, SentryTurretDeployRange) + .Any() + .ThrowIfTrue(ErrorCodes.BlobEmitterInRange); + var r = zone.IsInLineOfSight(ParentRobot, targetPosition, false); if (r.hit) @@ -133,13 +140,17 @@ protected override void OnAction() var ammo = GetAmmo() as RemoteControlledUnit; var fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + fieldTurret.Owner = this.ParentRobot.Eid; + HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); UseRemoteChannel(fieldTurret); var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); fieldTurret.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); - + + fieldTurret.SetGroup(bandwidthHandler); + var finder = new ClosestWalkablePositionFinder(zone, targetPosition); var position = finder.FindOrThrow(); var beamBuilder = Beam.NewBuilder() @@ -149,7 +160,18 @@ protected override void OnAction() fieldTurret.AddToZone(zone, position, ZoneEnterType.Default, beamBuilder); + var effectBuilder = this.ParentRobot.NewEffectBuilder(); + + SetupEffect(effectBuilder); + effectBuilder.WithToken(effectToken); + this.ParentRobot.ApplyEffect(effectBuilder); + ConsumeAmmo(); } + + protected void SetupEffect(EffectBuilder effectBuilder) + { + effectBuilder.SetType(EffectType.effect_remote_control); + } } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index a967a817a..6a1075f3d 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -276,7 +276,9 @@ + + @@ -665,6 +667,8 @@ + + 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 1512bd003..b3d27a6ae 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; @@ -25,7 +18,6 @@ using Perpetuum.Services.MissionEngine; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Services.MissionEngine.TransportAssignments; -using Perpetuum.Services.Strongholds; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Units.DockingBases; @@ -38,161 +30,55 @@ using Perpetuum.Zones.Effects; using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; -using Perpetuum.Zones.LandMines; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; 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 * 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) - { - // csak akkor kell ha csempevaltas volt - if (!_player.IsWalkable((int)nx, (int)ny)) - { - //_player.Zone.CreateAlignedDebugBeam(BeamType.red_10sec, new Position(nx, ny)); - break; - } - } - - px = nx; - py = ny; - //_player.Zone.CreateAlignedDebugBeam(BeamType.blue_10sec, new Position(px, py)); - } - _player.TryMove(new Position(px, py)); - //_player.Zone.CreateAlignedDebugBeam(BeamType.orange_10sec, new Position(px, py)); - } - } - - public class ErrorPacketBuilder : IBuilder - { - private readonly ErrorCodes _error; - - public ErrorPacketBuilder(ErrorCodes error) - { - _error = error; - } - - public Packet Build() - { - return new Packet(ZoneCommand.Error) {Error = _error}; - } - } - - public sealed class Player : Robot,IBlobableUnit,IBlobEmitter + 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; - private PlayerMoveCheckQueue _check; - private CancellableDespawnHelper _despawnHelper; - - 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); + 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; - _blobEmitter = new BlobEmitter(this); - _blobHandler = new BlobHandler(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; public long CorporationEid { get; set; } - public IZoneSession Session { get; private set; } - public Character Character { get; set; } = Character.None; - public bool HasGMStealth { get; set; } - - public bool TryMove(Position position) - { - if (!IsWalkable(position)) - return false; - _check.EnqueueMove(position); + public IZoneSession Session { get; private set; } - CurrentPosition = position; - return true; - } + 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); } @@ -205,10 +91,15 @@ public bool IsInSafeArea get { var zone = Zone; + if (zone == null) + { return false; + } - return zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area) || EffectHandler.ContainsEffect(EffectType.effect_safe_spot); + return zone.Configuration.Protected || + EffectHandler.ContainsEffect(EffectType.effect_syndicate_area) || + EffectHandler.ContainsEffect(EffectType.effect_safe_spot); } } @@ -219,7 +110,9 @@ public override bool IsLockable var isInvulnerable = IsInvulnerable; if (isInvulnerable) + { return false; + } return base.IsLockable; } @@ -227,57 +120,74 @@ public override bool IsLockable public IBlobHandler BlobHandler { - get { return _blobHandler; } + get { return blobHandler; } } public double BlobEmission { - get { return _blobEmitter.BlobEmission; } + get { return blobEmitter.BlobEmission; } } public double BlobEmissionRadius { - get { return _blobEmitter.BlobEmissionRadius; } + get { return blobEmitter.BlobEmissionRadius; } } + public Player( + IExtensionReader extensionReader, + ICorporationManager corporationManager, + MissionHandler.Factory missionHandlerFactory, + ITeleportStrategyFactories teleportStrategyFactories, + DockingBaseHelper dockingBaseHelper, + CombatLogger.Factory combatLoggerFactory) + { + 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); - public override void AcceptVisitor(IEntityVisitor visitor) - { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); + 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 - _check = PlayerMoveCheckQueue.Create(this, CurrentPosition); - - zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Visible, this)); - - MissionHandler = _missionHandlerFactory(zone, this); - MissionHandler.InitMissions(); - - Direction = FastRandom.NextDouble(); - - var p = DynamicProperties.GetProperty(k.pvpRemaining); - if (!p.HasValue) + if (affectedZoneId != -1 && Zone.Id != affectedZoneId) + { return; + } - ApplyPvPEffect(TimeSpan.FromMilliseconds(p.Value)); - p.Clear(); + ApplySelfTeleportEnablerEffect(duration); } - protected override void OnRemovedFromZone(IZone zone) + public bool TryMove(Position position) { - Session.SendPacket(ExitPacketBuilder); - zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); + if (!IsWalkable(position)) + { + return false; + } - _check.StopAndDispose(); + check.EnqueueMove(position); + CurrentPosition = position; - if (!States.LocalTeleport) - Session.Stop(); + return true; + } - base.OnRemovedFromZone(zone); + public void SetSession(IZoneSession session) + { + Session = session; + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } public override void OnUpdateToDb() @@ -292,24 +202,31 @@ public override void OnUpdateToDb() var zone = Zone; if (zone == null || States.Dead) + { return; + } var character = Character; + character.ZoneId = zone.Id; character.ZonePosition = CurrentPosition; var p = DynamicProperties.GetProperty(k.pvpRemaining); - var pvpEffect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); + if (pvpEffect == null) { p.Clear(); + return; } var effectTimer = pvpEffect.Timer; + if (effectTimer != null) + { p.Value = (int)effectTimer.Remaining.TotalMilliseconds; + } } finally { @@ -317,31 +234,19 @@ public override void OnUpdateToDb() } } - protected override void OnUpdate(TimeSpan time) - { - base.OnUpdate(time); - UpdateCombat(time); - _movement.Update(time); - _blobHandler.Update(time); - MissionHandler.Update(time); - - _combatLogger?.Update(time); - _despawnHelper?.Update(time, this); - } - public void SetStrongholdDespawn(TimeSpan time, UnitDespawnStrategy strategy) { - if (_despawnHelper == null) + if (despawnHelper == null) { - _despawnHelper = CancellableDespawnHelper.Create(this, time); - _despawnHelper.DespawnStrategy = strategy; + despawnHelper = CancellableDespawnHelper.Create(this, time); + despawnHelper.DespawnStrategy = strategy; } } public void ClearStrongholdDespawn() { - _despawnHelper?.Cancel(this); - _despawnHelper = null; + despawnHelper?.Cancel(this); + despawnHelper = null; } public void SendModuleProcessError(Module module, ErrorCodes error) @@ -349,7 +254,7 @@ public void SendModuleProcessError(Module module, ErrorCodes error) var 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); @@ -372,7 +277,9 @@ public void ApplyTeleportSicknessEffect() { var zone = Zone; if (zone == null || zone is TrainingZone) + { return; + } var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_teleport_sickness); @@ -384,18 +291,17 @@ public void ApplyTeleportSicknessEffect() 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(); + if (effect != null) + { EffectHandler.Remove(effect); + } var builder = NewEffectBuilder().SetType(EffectType.effect_teleport_self_enabler).WithDuration(duration); + ApplyEffect(builder); } @@ -406,7 +312,7 @@ 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); @@ -414,30 +320,32 @@ public void CheckDockingConditionsAndThrow(long baseEid, bool checkRange = true) } var zone = Zone; + if (zone == null) + { return; + } + + var 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; + if (!currentAccess.IsAdminOrGm()) { dockingBase.IsDockingAllowed(Character).ThrowIfError(); } - DockToBase(zone,dockingBase); + DockToBase(zone, dockingBase); } /// @@ -455,7 +363,7 @@ public void DockToBase(IZone zone, DockingBase dockingBase) publicContainer.AddItem(this, false); publicContainer.Save(); - dockingBase.DockIn(Character,NormalUndockDelay,ZoneExitType.Docked); + dockingBase.DockIn(Character, NormalUndockDelay, ZoneExitType.Docked); Transaction.Current.OnCommited(() => { @@ -465,330 +373,92 @@ public void DockToBase(IZone zone, DockingBase dockingBase) }); } - public static readonly TimeSpan NormalUndockDelay = TimeSpan.FromSeconds(7); + public ErrorCodes CheckPvp() + { + var zone = Zone; + Debug.Assert(zone != null); - protected override void OnDead(Unit killer) - { - HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); + if (!HasPvpEffect && (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area))) + { + return ErrorCodes.PvpIsNotAllowed; + } + + return 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 = 3; - public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 1; - - - //ennek mindenkepp vegig kell futnia - private void HandlePlayerDead(IZone zone, Unit killer) + public override ItemPropertyModifier GetPropertyModifier(AggregateField field) { - using (var scope = Db.CreateTransaction()) + var modifier = base.GetPropertyModifier(field); + + if (Character == Character.None) { - EnlistTransaction(); - try - { - killer = zone.ToPlayerOrGetOwnerPlayer(killer) ?? killer; + return modifier; + } - SaveCombatLog(zone, killer); + var characterExtensions = Character.GetExtensions(); + var extensions = extensionReader.GetExtensions(); + var extensionBonus = characterExtensions + .Select(e => extensions[e.id]) + .Where(e => e.aggregateField == field) + .Sum(e => characterExtensions.GetLevel(e.id) * e.bonus); - var character = Character; + extensionBonus += ExtensionBonuses + .Where(e => e.aggregateField == field) + .Sum(e => characterExtensions.GetLevel(e.extensionId) * e.bonus); - var dockingBase = character.GetHomeBaseOrCurrentBase(); - dockingBase.DockIn(character,NormalUndockDelay, ZoneExitType.Died); + if (!extensionBonus.IsZero()) + { + var m = ItemPropertyModifier.Create(field, extensionBonus); - PlayerDeathLogger.Log.Write(zone, this, killer); + m.NormalizeExtensionBonus(); + m.Modify(ref modifier); + } - //pay out insurance if needed - var wasInsured = InsuranceHelper.CheckInsuranceOnDeath(Eid, Definition); + return modifier; + } - if (!Session.AccessLevel.IsAdminOrGm()) - { - var robotInventory = GetContainer(); - Debug.Assert(robotInventory != null); + public override void OnAggression(Unit victim) + { + base.OnAggression(victim); - var lootItems = new List(); + 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); - } - - foreach (var item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) - { - //Transport assignments - var wrapper = item as VolumeWrapperContainer; - if (wrapper == null) - continue; - - lootItems.AddRange(wrapper.GetLootItems()); - wrapper.SetAllowDelete(); - Repository.Delete(wrapper); - } - - // 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 (item.Quantity > 0) - { - lootItems.Add(LootItemBuilder.Create(item.Definition).SetQuantity(item.Quantity).SetRepackaged(item.ED.AttributeFlags.Repackable).Build()); - } - else - { - robotInventory.RemoveItemOrThrow(item); - - //toroljuk is le, nem kell ez mar - Repository.Delete(item); - } - } - - //Check if paint was applied + paint drop chance - if (ED.Config.Tint != Tint && LootHelper.Roll(0.5)) - { - //Paint Query - //TODO: performance => cache paints in static collection - //TODO: performance => cache if painted, and paint entitydef on undock - EntityDefault paint = EntityDefault.Reader.GetAll() - .Where(i => i.CategoryFlags == CategoryFlags.cf_paints) - .Where(i => i.Config.Tint == Tint).First(); - if (paint != null) - { - lootItems.Add(LootItemBuilder.Create(paint.Definition).SetQuantity(1).SetDamaged(false).Build()); - } - } - - if (lootItems.Count > 0) - { - var lootContainer = LootContainer.Create().AddLoot(lootItems).BuildAndAddToZone(zone, CurrentPosition); - - 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); - } - } - } - - var 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(() => - { - var starterRobotInfo = new Dictionary - { - {k.baseEID, Eid}, - {k.robotEID, activeRobot.Eid} - }; - - Message.Builder.SetCommand(Commands.StarterRobotCreated).WithData(starterRobotInfo).ToCharacter(character).Send(); - }); - } - } - - character.SetActiveRobot(activeRobot); - } - else - { - // mert rendesek vagyunk - this.Repair(); - - // csak az adminok miatt kell - var container = dockingBase.GetPublicContainer(); - container.AddItem(this, false); - } - - this.Save(); - - scope.Complete(); - } - catch (Exception ex) - { - Logger.Exception(ex); - } - } - } - - protected override void OnTileChanged() - { - base.OnTileChanged(); - - var zone = Zone; - if (zone == null) - return; - - MissionHandler?.MissionUpdateOnTileChange(); - - var controlInfo = zone.Terrain.Controls.GetValue(CurrentPosition); - - ApplyHighwayEffect(controlInfo.IsAnyHighway); - - if (zone.Configuration.Protected) - return; - - //PVP zone - ApplySyndicateAreaEffect(controlInfo.SyndicateArea); - } - - protected override void OnCellChanged(CellCoord lastCellCoord, CellCoord currentCellCoord) - { - base.OnCellChanged(lastCellCoord,currentCellCoord); - - var zone = Zone; - if ( zone == null ) - return; - - Task.Run(() => - { - var district = currentCellCoord.ComputeDistrict(lastCellCoord); - zone.SendBeamsToPlayer(this,district); - Session.SendTerrainData(); - }).LogExceptions(); - } - - public ErrorCodes CheckPvp() - { - var zone = Zone; - Debug.Assert(zone != null); - - if (!HasPvpEffect && (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area))) - return ErrorCodes.PvpIsNotAllowed; - - return ErrorCodes.NoError; - } - - private void ApplySyndicateAreaEffect(bool apply) - { - if ( apply ) - { - var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_syndicate_area); - ApplyEffect(effectBuilder); - } - else - { - EffectHandler.RemoveEffectsByType(EffectType.effect_syndicate_area); - } - } - - private void ApplyHighwayEffect(bool apply) - { - if (apply) - { - var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_highway) - .WithPropertyModifier(ItemPropertyModifier.Create(AggregateField.effect_speed_highway_modifier, 1.0)); - ApplyEffect(effectBuilder); - } - else - { - EffectHandler.RemoveEffectsByType(EffectType.effect_highway); - } - } - - private bool IsInSameCorporation(Player player) - { - return (CorporationEid == player.CorporationEid) && !IsInDefaultCorporation(); - } - - public bool IsInDefaultCorporation() - { - return DefaultCorporationDataCache.IsCorporationDefault(CorporationEid); - } - - public override ItemPropertyModifier GetPropertyModifier(AggregateField field) - { - var modifier = base.GetPropertyModifier(field); - - if (Character == Character.None) - return modifier; - - var characterExtensions = Character.GetExtensions(); - var extensions = _extensionReader.GetExtensions(); - - var extensionBonus = characterExtensions.Select(e => extensions[e.id]) - .Where(e => e.aggregateField == field) - .Sum(e => characterExtensions.GetLevel(e.id) * e.bonus); - - extensionBonus += ExtensionBonuses.Where(e => e.aggregateField == field).Sum(e => characterExtensions.GetLevel(e.extensionId) * e.bonus); - - if (!extensionBonus.IsZero()) - { - var m = ItemPropertyModifier.Create(field,extensionBonus); - m.NormalizeExtensionBonus(); - m.Modify(ref modifier); - } - - return modifier; - } - - private bool IsUnitPVPAggro(Unit unit) - { - return unit is MobileTeleport - || unit is IPBSObject - || unit is WallHealer - || unit is ProximityDeviceBase - || (unit is BlobEmitterUnit b && b.IsPlayerSpawned); - } - - public override void OnAggression(Unit victim) - { - base.OnAggression(victim); - - AddInCombatWith(victim); - - if (victim is ITaggable taggable) - taggable.Tag(this, TimeSpan.Zero); - - if (IsUnitPVPAggro(victim)) - { - ApplyPvPEffect(); - return; - } - - if (!(victim is Player victimPlayer)) - return; + if (!(victim is Player victimPlayer)) + { + return; + } victimPlayer.Session.CancelLogout(); ApplyPvPEffect(); if (IsInSameCorporation(victimPlayer)) + { return; + } if (HasPvpEffect && victimPlayer.HasPvpEffect) + { return; + } } public void OnPvpSupport(Unit target) @@ -807,18 +477,15 @@ public override string InfoString public void SendInitSelf() { var zone = Zone; + Debug.Assert(zone != null, "zone != null"); Session.SendPacket(EnterPacketBuilder); - - // terrain Session.SendTerrainData(); + zone.SendBeamsToPlayer(this, GridDistricts.All); - // beams - zone.SendBeamsToPlayer(this,GridDistricts.All); - - // elkuldjuk az osszes lockot var lockPackets = GetLockPackets(); + Session.SendPackets(lockPackets); foreach (var visibility in GetVisibleUnits()) @@ -826,10 +493,14 @@ public void SendInitSelf() Session.SendPacket(visibility.Target.EnterPacketBuilder); var robot = visibility.Target as Robot; + if (robot == null) + { continue; + } var unitLockPackets = robot.GetLockPackets(); + Session.SendPackets(unitLockPackets); } @@ -859,190 +530,53 @@ public void WriteFQLog(string message) Logger.Log(e); } - private void SendError(ErrorCodes error) - { - Session.SendPacket(new ErrorPacketBuilder(error)); - } - public void SendForceUpdate() { Session.SendPacket(new UnitUpdatePacketBuilder(this, UpdatePacketControl.ForceReposition)); } - - protected override void OnBroadcastPacket(IBuilder packetBuilder) - { - base.OnBroadcastPacket(packetBuilder); - Session.SendPacket(packetBuilder.Build()); - } - - protected override void OnUnitVisibilityUpdated(Unit target,Visibility visibility) + public override void OnCombatEvent(Unit source, CombatEventArgs e) { - switch (visibility) - { - case Visibility.Visible: - { - 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; - } + base.OnCombatEvent(source, e); - } + var player = Zone.ToPlayerOrGetOwnerPlayer(source); - if (Gang.IsMember(target)) + if (player == null) { - Session.SendPacket(new GangUpdatePacketBuilder(visibility,(Player) target)); + return; } - base.OnUnitVisibilityUpdated(target,visibility); + var logger = LazyInitializer.EnsureInitialized(ref combatLogger, CreateCombatLogger); + + logger.Log(player, e); } - 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); - } - - protected override void OnLockStateChanged(Lock @lock) - { - base.OnLockStateChanged(@lock); - - if (@lock is UnitLock u) - { - var 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) - { - MissionHandler.EnqueueMissionEventInfo(new LockUnitEventInfo(this, npc, npc.CurrentPosition)); - MissionHandler.SignalParticipationByLocking(npc.GetMissionGuid()); - } - } - } - - if (@lock.State == LockState.Inprogress && EffectHandler.ContainsEffect(EffectType.effect_invulnerable)) - { - EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); - } - } - - protected override void OnEffectChanged(Effect effect, bool apply) - { - base.OnEffectChanged(effect,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()); - break; - } - } - } - - protected override void OnLockError(Lock @lock, ErrorCodes error) - { - SendError(error); - base.OnLockError(@lock, error); - } - - public override void OnCombatEvent(Unit source, CombatEventArgs e) - { - base.OnCombatEvent(source, e); - - var player = Zone.ToPlayerOrGetOwnerPlayer(source); - if (player == null) - return; - - var logger = LazyInitializer.EnsureInitialized(ref _combatLogger, CreateCombatLogger); - logger.Log(player,e); - } - - private CombatLogger CreateCombatLogger() - { - var logger = _combatLoggerFactory(this); - logger.Expired = () => - { - _combatLogger = null; - }; - return logger; - } - - private void SaveCombatLog(IZone zone, Unit killer) - { - _combatLogger?.Save(zone,killer); - } - - public static Player LoadPlayerAndAddToZone(IZone zone,Character character) + public static Player LoadPlayerAndAddToZone(IZone zone, Character character) { using (var scope = Db.CreateTransaction()) { var player = (Player)character.GetActiveRobot().ThrowIfNull(ErrorCodes.ARobotMustBeSelected); DockingBase dockingBase = null; - ZoneEnterType zoneEnterType; Position spawnPosition; + if (character.IsDocked) { - // ha bazisrol jott zoneEnterType = ZoneEnterType.Undock; - dockingBase = character.GetCurrentDockingBase(); spawnPosition = UndockSpawnPositionSelector.SelectSpawnPosition(dockingBase); - character.ZoneId = zone.Id; character.ZonePosition = spawnPosition; character.IsDocked = false; } else { - // ha teleportalt zoneEnterType = ZoneEnterType.Teleport; - zone.Id.ThrowIfNotEqual(character.ZoneId ?? -1, ErrorCodes.InvalidZoneId); var zonePosition = character.ZonePosition.ThrowIfNull(ErrorCodes.InvalidPosition); + spawnPosition = (Position)zonePosition; } @@ -1068,7 +602,7 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) player.CorporationEid = character.CorporationEid; zone.SetGang(player); - player.AddToZone(zone,validPosition,zoneEnterType); + player.AddToZone(zone, validPosition, zoneEnterType); player.ApplyInvulnerableEffect(); }); @@ -1081,78 +615,26 @@ public static Player LoadPlayerAndAddToZone(IZone zone,Character character) public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, bool applyInvulnerable) { var zone = Zone; + if (zone == null) + { return null; + } + + var teleport = teleportStrategyFactories.TeleportWithinZoneFactory(); - var teleport = _teleportStrategyFactories.TeleportWithinZoneFactory(); if (teleport == null) + { return null; + } teleport.TargetPosition = target; teleport.ApplyTeleportSickness = applyTeleportSickness; teleport.ApplyInvulnerable = applyInvulnerable; - var task = teleport.DoTeleportAsync(this); - return task?.LogExceptions(); - } - - public override void UpdateVisibilityOf(Unit target) - { - target.UpdatePlayerVisibility(this); - } - - protected override void UpdateUnitVisibility(Unit target) - { - UpdateVisibility(target); - } - - protected internal override void UpdatePlayerVisibility(Player player) - { - UpdateVisibility(player); - } - - protected override bool IsDetected(Unit target) - { - if ( Gang.IsMember(target) ) - return true; - return base.IsDetected(target); - } - - protected override bool IsHostileFor(Unit unit) - { - return unit.IsHostile(this); - } - - private void UpdateCombat(TimeSpan time) - { - if (!States.Combat) - return; - - _combatTimer.Update(time); - - if ( !_combatTimer.Passed) - return; - - SetCombatState(false); - } - - private void SetCombatState(bool state) - { - States.Combat = state; - _combatTimer.Reset(); - - if ( state ) - Session.ResetLogoutTimer(); - } - - private readonly IntervalTimer _combatTimer = new IntervalTimer(TimeSpan.FromSeconds(10)); - - private void AddInCombatWith(Unit enemy) - { - SetCombatState(true); + var task = teleport.DoTeleportAsync(this); - var enemyPlayer = enemy as Player; - enemyPlayer?.SetCombatState(true); + return task?.LogExceptions(); } public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) @@ -1163,13 +645,13 @@ public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) data.Add(k.started, (long)start.TotalMilliseconds); data.Add(k.now, (long)start.TotalMilliseconds); - Message.Builder.SetCommand(Commands.AlarmStart).WithData(data).ToCharacter(Character).Send(); } - public void SendEndProgressBar( Unit unit, bool success = true) + 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(); } @@ -1177,34 +659,26 @@ public void SendEndProgressBar( Unit unit, bool success = true) public void SendArtifactRadarBeam(Position targetPosition) { var builder = Beam.NewBuilder() - .WithType(BeamType.artifact_radar) - .WithSourcePosition(targetPosition) - .WithTargetPosition(targetPosition) - .WithState(BeamState.AlignToTerrain) - .WithDuration(TimeSpan.FromSeconds(30)); + .WithType(BeamType.artifact_radar) + .WithSourcePosition(targetPosition) + .WithTargetPosition(targetPosition) + .WithState(BeamState.AlignToTerrain) + .WithDuration(TimeSpan.FromSeconds(30)); Session.SendBeam(builder); } public bool IsStandingMatch(long targetCorporationEid, double? standingLimit) { - return _corporationManager.IsStandingMatch(targetCorporationEid, CorporationEid, standingLimit); + return corporationManager.IsStandingMatch(targetCorporationEid, CorporationEid, standingLimit); } public void ReloadContainer() { - void Reload() - { - var container = GetContainer(); - Debug.Assert(container != null,"container != null"); - - container.EnlistTransaction(); - container.ReloadItems(Character); - container.SendUpdateToOwner(); - } - if (Transaction.Current != null) + { Reload(); + } else { using (var scope = Db.CreateTransaction()) @@ -1220,10 +694,13 @@ public void UpdateCorporationOnZone(long newCorporationEid) CorporationEid = newCorporationEid; var playersOnZone = Zone.GetCharacters().ToArray(); + if (playersOnZone.Length <= 0) + { return; + } - var result = new Dictionary + var result = new Dictionary { {k.corporationEID, newCorporationEid}, {k.characterID, Character.Id}, @@ -1232,14 +709,555 @@ public void UpdateCorporationOnZone(long newCorporationEid) Message.Builder.SetCommand(Commands.CharacterUpdate).WithData(result).ToCharacters(playersOnZone).Send(); } - public void EnableSelfTeleport(TimeSpan duration, int affectedZoneId = -1) + public override void UpdateVisibilityOf(Unit target) { - if (affectedZoneId != -1 && Zone.Id != affectedZoneId) + target.UpdatePlayerVisibility(this); + } + + protected override void OnDead(Unit killer) + { + HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); + } + + protected override void OnTileChanged() + { + base.OnTileChanged(); + + var zone = Zone; + + if (zone == null) + { return; + } - ApplySelfTeleportEnablerEffect(duration); + MissionHandler?.MissionUpdateOnTileChange(); + + var controlInfo = zone.Terrain.Controls.GetValue(CurrentPosition); + + ApplyHighwayEffect(controlInfo.IsAnyHighway); + + if (zone.Configuration.Protected) + { + return; + } + + ApplySyndicateAreaEffect(controlInfo.SyndicateArea); + } + + protected override void OnCellChanged(CellCoord lastCellCoord, CellCoord currentCellCoord) + { + base.OnCellChanged(lastCellCoord, currentCellCoord); + + var zone = Zone; + + if (zone == null) + { + return; + } + + Task.Run(() => + { + var district = currentCellCoord.ComputeDistrict(lastCellCoord); + zone.SendBeamsToPlayer(this, district); + Session.SendTerrainData(); + }).LogExceptions(); + } + + protected override void UpdateUnitVisibility(Unit target) + { + UpdateVisibility(target); + } + + protected internal override void UpdatePlayerVisibility(Player player) + { + UpdateVisibility(player); + } + + protected override bool IsDetected(Unit target) + { + if (Gang.IsMember(target)) + { + return true; + } + + return base.IsDetected(target); + } + + protected override bool IsHostileFor(Unit unit) + { + 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) + { + switch (visibility) + { + case Visibility.Visible: + { + 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; + } + + } + + if (Gang.IsMember(target)) + { + Session.SendPacket(new GangUpdatePacketBuilder(visibility, (Player)target)); + } + + base.OnUnitVisibilityUpdated(target, visibility); + } + + protected override void OnLockStateChanged(Lock @lock) + { + base.OnLockStateChanged(@lock); + + if (@lock is UnitLock u) + { + var player = u.Target as Player; + + player?.Session.CancelLogout(); + + if (@lock.State == LockState.Locked) + { + var npc = u.Target as Npc; + + if (npc != null) + { + MissionHandler.EnqueueMissionEventInfo(new LockUnitEventInfo(this, npc, npc.CurrentPosition)); + MissionHandler.SignalParticipationByLocking(npc.GetMissionGuid()); + } + } + } + + if (@lock.State == LockState.Inprogress && EffectHandler.ContainsEffect(EffectType.effect_invulnerable)) + { + EffectHandler.RemoveEffectsByType(EffectType.effect_invulnerable); + } + } + + protected override void OnEffectChanged(Effect effect, bool apply) + { + base.OnEffectChanged(effect, 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()); + + break; + } + } + } + + protected override void OnLockError(Lock @lock, ErrorCodes error) + { + SendError(error); + base.OnLockError(@lock, error); + } + + protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) + { + 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 p = DynamicProperties.GetProperty(k.pvpRemaining); + + if (!p.HasValue) + { + return; + } + + ApplyPvPEffect(TimeSpan.FromMilliseconds(p.Value)); + p.Clear(); + } + + protected override void OnRemovedFromZone(IZone zone) + { + Session.SendPacket(ExitPacketBuilder); + zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); + + check.StopAndDispose(); + + if (!States.LocalTeleport) + { + Session.Stop(); + } + + base.OnRemovedFromZone(zone); + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + UpdateCombat(time); + movement.Update(time); + blobHandler.Update(time); + MissionHandler.Update(time); + + combatLogger?.Update(time); + despawnHelper?.Update(time, this); + } + + 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); + } + + private bool HasAggressorEffect + { + get { return EffectHandler.ContainsEffect(EffectType.effect_aggressor); } + } + + private void ApplyAggressorEffect() + { + ApplyEffect(NewEffectBuilder().SetType(EffectType.effect_aggressor)); + } + + private void UpdateCombat(TimeSpan time) + { + if (!States.Combat) + { + return; + } + + combatTimer.Update(time); + + if (!combatTimer.Passed) + { + return; + } + + SetCombatState(false); + } + + private void SetCombatState(bool state) + { + States.Combat = state; + combatTimer.Reset(); + + if (state) + { + Session.ResetLogoutTimer(); + } + } + + private void AddInCombatWith(Unit enemy) + { + SetCombatState(true); + + var enemyPlayer = enemy as Player; + + enemyPlayer?.SetCombatState(true); + } + + private void Reload() + { + var container = GetContainer(); + + Debug.Assert(container != null, "container != null"); + container.EnlistTransaction(); + container.ReloadItems(Character); + container.SendUpdateToOwner(); + } + + private CombatLogger CreateCombatLogger() + { + var logger = combatLoggerFactory(this); + + logger.Expired = () => + { + combatLogger = null; + }; + + return logger; + } + + private void SaveCombatLog(IZone zone, Unit killer) + { + combatLogger?.Save(zone, killer); + } + + private void ApplySyndicateAreaEffect(bool apply) + { + if (apply) + { + var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_syndicate_area); + ApplyEffect(effectBuilder); + } + else + { + EffectHandler.RemoveEffectsByType(EffectType.effect_syndicate_area); + } + } + + private void ApplyHighwayEffect(bool apply) + { + if (apply) + { + var effectBuilder = NewEffectBuilder() + .SetType(EffectType.effect_highway) + .WithPropertyModifier(ItemPropertyModifier.Create(AggregateField.effect_speed_highway_modifier, 1.0)); + + ApplyEffect(effectBuilder); + } + else + { + EffectHandler.RemoveEffectsByType(EffectType.effect_highway); + } + } + + private bool IsInSameCorporation(Player player) + { + return (CorporationEid == player.CorporationEid) && !IsInDefaultCorporation(); + } + + private void SendError(ErrorCodes error) + { + Session.SendPacket(new ErrorPacketBuilder(error)); + } + + private bool IsUnitPVPAggro(Unit unit) + { + return unit is MobileTeleport || + unit is IPBSObject || + unit is WallHealer || + unit is ProximityDeviceBase || + (unit is BlobEmitterUnit b && b.IsPlayerSpawned); + } + + private Task HandlePlayerDeadAsync(IZone zone, Unit killer) + { + return Task.Run(() => HandlePlayerDead(zone, killer)); + } + + private void HandlePlayerDead(IZone zone, Unit killer) + { + using (var scope = Db.CreateTransaction()) + { + EnlistTransaction(); + + try + { + killer = zone.ToPlayerOrGetOwnerPlayer(killer) ?? killer; + + SaveCombatLog(zone, killer); + + var character = Character; + var dockingBase = character.GetHomeBaseOrCurrentBase(); + + dockingBase.DockIn(character, NormalUndockDelay, ZoneExitType.Died); + + PlayerDeathLogger.Log.Write(zone, this, killer); + + var wasInsured = InsuranceHelper.CheckInsuranceOnDeath(Eid, Definition); + + if (!Session.AccessLevel.IsAdminOrGm()) + { + var robotInventory = GetContainer(); + + Debug.Assert(robotInventory != null); + + var lootItems = new List(); + + foreach (var module in Modules.Where(m => LootHelper.Roll())) + { + lootItems.Add(LootItemBuilder.Create(module).AsDamaged().Build()); + + var activeModule = module as ActiveModule; + var ammo = activeModule?.GetAmmo(); + + if (ammo != null && LootHelper.Roll()) + { + lootItems.Add(LootItemBuilder.Create(ammo).Build()); + } + + module.Parent = robotInventory.Eid; + + Repository.Delete(module); + } + + foreach (var item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) + { + var wrapper = item as VolumeWrapperContainer; + + if (wrapper == null) + { + continue; + } + + lootItems.AddRange(wrapper.GetLootItems()); + wrapper.SetAllowDelete(); + Repository.Delete(wrapper); + } + + 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 (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); + } + } + + 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(); + + if (paint != null) + { + lootItems.Add(LootItemBuilder.Create(paint.Definition).SetQuantity(1).SetDamaged(false).Build()); + } + } + + if (lootItems.Count > 0) + { + var lootContainer = LootContainer.Create() + .AddLoot(lootItems) + .BuildAndAddToZone(zone, CurrentPosition); + + 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); + } + } + } + + var 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(() => + { + var 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(); + + var container = dockingBase.GetPublicContainer(); + + container.AddItem(this, false); + } + + this.Save(); + + scope.Complete(); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + } + } } } 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/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 9e293a445..608a91b2a 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; @@ -24,13 +15,22 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Effects; using Perpetuum.Zones.Eggs; -using Perpetuum.Zones.RemoteControl; 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.NpcSystem; +using Perpetuum.Zones.RemoteControl; +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 { @@ -50,13 +50,13 @@ protected override double CalculateValue() var 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; } @@ -76,10 +76,10 @@ 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 { @@ -119,7 +119,7 @@ public abstract partial class Unit : Item private ItemProperty _reactorRadiation; private ItemProperty _signatureRadius; private ItemProperty _slope; - + private readonly Lazy _height; protected Unit() @@ -187,7 +187,7 @@ public double CurrentSpeed get => _currentSpeed; set { - if ( Math.Abs(_currentSpeed - value) < double.Epsilon ) + if (Math.Abs(_currentSpeed - value) < double.Epsilon) return; _currentSpeed = value; @@ -200,7 +200,7 @@ public double Direction get => _direction; set { - if ( Math.Abs(_direction - value) < double.Epsilon ) + if (Math.Abs(_direction - value) < double.Epsilon) return; _direction = value; @@ -213,7 +213,7 @@ public double Orientation get => _orientation; set { - if ( Math.Abs(_orientation - value) < double.Epsilon ) + if (Math.Abs(_orientation - value) < double.Epsilon) return; _orientation = value; @@ -229,7 +229,7 @@ public virtual ErrorCodes IsAttackable { get { - if ( !ED.AttributeFlags.NonAttackable && !States.Dead) + if (!ED.AttributeFlags.NonAttackable && !States.Dead) return ErrorCodes.NoError; return ErrorCodes.TargetIsNonAttackable; @@ -252,6 +252,8 @@ public bool IsInvulnerable public bool HasNoTeleportWhilePVP => EffectHandler.Effects.Any(e => e.PropertyModifiers.Any(p => p.Field == AggregateField.pvp_no_teleport)); + public bool HasRemoteControlEffect => EffectHandler.ContainsEffect(EffectType.effect_remote_control); + public Position WorldPosition { get { return Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; } @@ -300,7 +302,7 @@ public Position CurrentPosition /// public void Update(TimeSpan time) { - if ( !InZone || States.Dead ) + if (!InZone || States.Dead) return; OnUpdate(time); @@ -310,7 +312,7 @@ public void Update(TimeSpan time) protected virtual void OnUpdate(TimeSpan time) { - _coreRecharger.RechargeCore(this,time); + _coreRecharger.RechargeCore(this, time); EffectHandler.Update(time); @@ -362,7 +364,7 @@ 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); @@ -376,15 +378,15 @@ protected virtual void OnEffectChanged(Effect effect,bool apply) 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); @@ -403,7 +405,7 @@ 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"); @@ -429,7 +431,7 @@ 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); packet.AppendByte((byte)(e.IsCritical ? 1 : 0)); @@ -442,7 +444,7 @@ protected virtual void OnDamageTaken(Unit source, DamageTakenEventArgs e) Armor -= e.TotalDamage; - OnCombatEvent(source,e); + OnCombatEvent(source, e); if (Armor <= 0.0) { @@ -452,7 +454,7 @@ protected virtual void OnDamageTaken(Unit source, DamageTakenEventArgs e) public virtual void OnCombatEvent(Unit source, CombatEventArgs e) { - + } @@ -476,10 +478,10 @@ private void OnUpdated(UnitUpdatedEventArgs e) protected virtual void DoExplosion() { var zone = Zone; - if ( zone == null ) + if (zone == null) return; - if ( zone.Configuration.Protected ) + if (zone.Configuration.Protected) return; var damageBuilder = GetExplosionDamageBuilder(); @@ -521,7 +523,7 @@ private IDamageBuilder GetExplosionDamageBuilder() return damageBuilder; } - private class KillDetectorHelper : IEntityVisitor,IEntityVisitor,IEntityVisitor + private class KillDetectorHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor { public KillDetectorHelper() { @@ -554,7 +556,7 @@ public void Visit(PBSObject pbsObject) public void Kill(Unit killer = null) { - if (!Monitor.TryEnter(_killSync) ) + if (!Monitor.TryEnter(_killSync)) return; try @@ -591,11 +593,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(); @@ -620,13 +622,13 @@ 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(); - if (!InZone) + if (!InZone) return result; result.Add(k.px, CurrentPosition.X); @@ -711,17 +713,17 @@ public void ApplyPvPEffect(TimeSpan duration) 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() @@ -761,7 +763,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; @@ -800,7 +802,7 @@ public Packet Build() if (primaryLock != null) { packet.AppendByte(1); - LockPacketBuilder.AppendTo(primaryLock,packet); + LockPacketBuilder.AppendTo(primaryLock, packet); } else { @@ -851,12 +853,12 @@ 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++) { @@ -873,7 +875,7 @@ private static void WriteModule(BinaryWriter bw, Module module) public static IBuilder Create(Unit unit, ZoneEnterType enterType) { - return new UnitEnterPacketBuilder(unit,enterType); + return new UnitEnterPacketBuilder(unit, enterType); } } @@ -920,13 +922,13 @@ protected 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; } + 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; } internal virtual bool IsHostile(SentryTurret turret) { return false; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index a470ea43f..9a84384d8 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -18,7 +18,9 @@ public BodyPullThreatHelper(SmartCreature smartCreature) public void Visit(Player player) { - if (smartCreature.Behavior.Type != BehaviorType.Aggressive) + if (smartCreature.Behavior.Type != BehaviorType.Aggressive && + smartCreature.Behavior.Type != BehaviorType.RemoteControlled && + !player.HasRemoteControlEffect) { return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs new file mode 100644 index 000000000..6efcb33de --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs @@ -0,0 +1,21 @@ +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class SentryTurretCombatAI : StationaryCombatAI + { + public SentryTurretCombatAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override PrimaryLockSelectionStrategySelector InitSelector() + { + return PrimaryLockSelectionStrategySelector.Create() + .WithStrategy(PrimaryLockSelectionStrategy.HostileOrClosest, 1) + .Build(); + + //return PrimaryLockSelectionStrategySelector.Create() + // .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 1) + // .WithStrategy(PrimaryLockSelectionStrategy.Closest, 2) + // .WithStrategy(PrimaryLockSelectionStrategy.OptimalRange, 3) + // .WithStrategy(PrimaryLockSelectionStrategy.Random, 10) + // .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..5e194f35a --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs @@ -0,0 +1,17 @@ +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 index 07da38a8d..4e86aa363 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs @@ -6,6 +6,7 @@ namespace Perpetuum.Zones.NpcSystem.AI public class StationaryCombatAI : CombatAI { private readonly IntervalTimer updateFrequency = new IntervalTimer(650); + public StationaryCombatAI(SmartCreature smartCreature) : base(smartCreature) { } protected override PrimaryLockSelectionStrategySelector InitSelector() @@ -32,7 +33,7 @@ private void FindHostiles(TimeSpan time) if (updateFrequency.Passed) { updateFrequency.Reset(); - this.smartCreature.LookingForHostiles(); + smartCreature.LookingForHostiles(); } } diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs index efabda7f5..4e25290d0 100644 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs @@ -6,5 +6,6 @@ public enum PrimaryLockSelectionStrategy Hostile, Closest, OptimalRange, + HostileOrClosest, } } diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs index 46369cb67..25f03f47b 100644 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs @@ -14,7 +14,8 @@ public static class Strategies { PrimaryLockSelectionStrategy.Random, TargetRandom }, { PrimaryLockSelectionStrategy.Hostile, TargetMostHated }, { PrimaryLockSelectionStrategy.Closest, TargetClosest }, - { PrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal } + { PrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal }, + { PrimaryLockSelectionStrategy.HostileOrClosest, TargetMostHatedOrClosest }, }; public static TargetSelectionStrategy GetStrategy(PrimaryLockSelectionStrategy strategyType) @@ -64,6 +65,11 @@ 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) diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 326ea4919..56daf10a6 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -9,6 +9,7 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem.AI; using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.RemoteControl; using System; using System.Linq; @@ -18,7 +19,7 @@ public class SmartCreature : Creature { private const double AggroRange = 30; private const double BestComnatRangeModifier = 0.9; - private const double CallForHelpArmorThreshold = 0.2; + private const double BaseCallForHelpArmorThreshold = 0.2; private readonly ThreatManager threatManager; private readonly PseudoThreatManager pseudoThreatManager; private readonly TimeKeeper debounceBodyPull = new TimeKeeper(TimeSpan.FromSeconds(2.5)); @@ -74,6 +75,8 @@ public virtual bool IsStationary get { return MaxSpeed.IsZero(); } } + public virtual double CallForHelpArmorThreshold => BaseCallForHelpArmorThreshold; + public bool CallForHelp { private get; set; } public NpcBossInfo BossInfo { get; set; } @@ -293,7 +296,9 @@ public void AddAssistThreat(Unit assistant, Unit target, Threat threat) } if (!CanAddThreatTo(assistant, threat)) + { return; + } AddThreat(assistant, threat, true); } @@ -301,7 +306,9 @@ public void AddAssistThreat(Unit assistant, Unit target, Threat threat) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void OnTileChanged() @@ -316,7 +323,11 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) base.OnEnterZone(zone, enterType); - if (IsStationary) + if (this is SentryTurret) + { + AI.Push(new SentryTurretCombatAI(this)); + } + else if (IsStationary) { AI.Push(new StationaryIdleAI(this)); } @@ -331,6 +342,7 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) base.OnDamageTaken(source, e); var player = Zone.ToPlayerOrGetOwnerPlayer(source); + if (player == null) { return; @@ -373,11 +385,16 @@ private void CallingForHelp() } 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)) { diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index 2ce746ac2..fba89e210 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -1,23 +1,16 @@ -using Perpetuum.Accounting.Characters; -using Perpetuum.ExportedTypes; -using Perpetuum.Items; -using Perpetuum.Robots; +using Perpetuum.Items; +using Perpetuum.Modules; using Perpetuum.Units; -using Perpetuum.Zones.Blobs; -using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.Locking; -using System; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.Flocks; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; -using Perpetuum.Modules; namespace Perpetuum.Zones.RemoteControl { - public class BandwidthHandler + public class BandwidthHandler : ISmartCreatureGroup { private readonly RemoteControllerModule owner; private List channels = new List(); @@ -25,13 +18,17 @@ public class BandwidthHandler private readonly ConcurrentQueue deactivatedChannels = new ConcurrentQueue(); private int dirty; + public string Name => owner.GetCharacter().Nick; + + public IEnumerable Members => channels.Select(x => x.Turret); + public BandwidthHandler(RemoteControllerModule owner) { this.owner = owner; } - public double BandwidthUsed - { + public double BandwidthUsed + { get { return channels.Any() @@ -64,11 +61,14 @@ public void UseRemoteChannel(RemoteChannel newChannel) public void OnRemoteChannelDeactivated(SentryTurret turret) { var channel = GetRemoteChannelByUnit(turret); + if (channel != null) { ReleaseRemoteChannel(channel); turret.RemoteChannelDeactivated -= OnRemoteChannelDeactivated; } + + Update(); } private void ReleaseRemoteChannel(RemoteChannel channel) @@ -80,6 +80,7 @@ private void ReleaseRemoteChannel(RemoteChannel channel) private void ProcessReleasedChannels(IList channels) { RemoteChannel releasedChannel; + while (deactivatedChannels.TryDequeue(out releasedChannel)) { channels.Remove(releasedChannel); @@ -91,6 +92,7 @@ public void Update() if (Interlocked.CompareExchange(ref dirty, 0, 1) == 1) { var channelsToProcess = this.channels.ToList(); + try { ProcessReleasedChannels(channelsToProcess); @@ -134,6 +136,11 @@ public RemoteChannel GetRemoteChannelByUnit(Unit unit) return GetRemoteChannelByEid(unit.Eid); } + public void AddDebugInfoToDictionary(IDictionary dictionary) + { + // Do nothing + } + [CanBeNull] private RemoteChannel GetRemoteChannelByEid(long unitEid) { diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index e548a8bdd..12023f9d3 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -1,6 +1,8 @@ using Perpetuum.ExportedTypes; +using Perpetuum.Groups.Corporations; using Perpetuum.Items; using Perpetuum.Players; +using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.Beams; using Perpetuum.Zones.NpcSystem; @@ -12,6 +14,9 @@ namespace Perpetuum.Zones.RemoteControl public class SentryTurret : SmartCreature { private UnitDespawnHelper despawnHelper; + private const double SentryTurretCallForHelpArmorThreshold = 0.8; + private readonly IStandingHandler standingHandler; + private const double StandingLimit = 0.0; public event RemoteChannelEventHandler RemoteChannelDeactivated; @@ -27,6 +32,11 @@ public TimeSpan DespawnTime set { despawnHelper = UnitDespawnHelper.Create(this, value); } } + public SentryTurret(IStandingHandler standingHandler) + { + this.standingHandler = standingHandler; + } + public override void Initialize() { rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); @@ -38,6 +48,8 @@ public override void Initialize() public override bool IsStationary => true; + public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; + protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); @@ -51,7 +63,12 @@ protected override bool IsHostileFor(Unit unit) internal override bool IsHostile(Player player) { - return false; + if (Owner == player.Eid) + { + return false; + } + + return IsHostileCorporation(player.CorporationEid); } internal override bool IsHostile(Npc npc) @@ -83,5 +100,17 @@ protected override void OnDead(Unit killer) base.OnDead(killer); } + + private bool IsHostileCorporation(long corporationEid) + { + if (DefaultCorporationDataCache.IsCorporationDefault(corporationEid)) + { + return true; + } + + var standing = standingHandler.GetStanding(Owner, corporationEid); + + return StandingLimit >= standing; + } } } From c5e38dcff4f67a46b0d79a16f26c598cd040bbb7 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Mon, 4 Sep 2023 17:33:17 +0300 Subject: [PATCH 407/457] =?UTF-8?q?=EF=BB=BFSpark=20Teleport=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renaming Spark Teleport Token into Spark Teleport Device --- .../PerpetuumBootstrapper.cs | 242 +++++++++--------- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1 + src/Perpetuum/Items/SparkTeleportDevice.cs | 25 ++ 3 files changed, 147 insertions(+), 121 deletions(-) create mode 100644 src/Perpetuum/Items/SparkTeleportDevice.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 68c6f6a04..df7a26d36 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; @@ -113,6 +100,7 @@ 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; @@ -133,10 +121,13 @@ using Perpetuum.Zones.Intrusion; using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.NpcSystem.Flocks; -using Perpetuum.Zones.NpcSystem.Reinforcements; 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; @@ -168,6 +159,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; @@ -182,11 +186,6 @@ using TrashItems = Perpetuum.RequestHandlers.TrashItems; using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; -using Perpetuum.Services.Strongholds; -using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; -using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; -using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; -using Perpetuum.Items.Helpers; namespace Perpetuum.Bootstrapper { @@ -262,13 +261,13 @@ public void WriteCommandsToFile(string path) { 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) @@ -304,7 +303,7 @@ public void Init(string gameRoot) CorporationData.CorporationManager = _container.Resolve(); Character.CharacterFactory = _container.Resolve(); - Character.CharacterCache = _container.Resolve>().Invoke("CharacterCache"); + Character.CharacterCache = _container.Resolve>().Invoke("CharacterCache"); MissionHelper.Init(_container.Resolve(), _container.Resolve()); MissionHelper.MissionProcessor = _container.Resolve(); @@ -352,18 +351,18 @@ 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; + } } }; @@ -398,7 +397,7 @@ public bool TryInitUpnp(out bool success) void Map(int port) { - var task = natDevice.CreatePortMapAsync(new Mapping(Protocol.Tcp,port,port)).ContinueWith(t => + var task = natDevice.CreatePortMapAsync(new Mapping(Protocol.Tcp, port, port)).ContinueWith(t => { Logger.Info($"[UPNP] Port mapped: {port}"); }); @@ -507,7 +506,7 @@ private void InitContainer(string gameRoot) { var cache = new MemoryCache("CharacterProfiles"); return new CachedReadOnlyRepository(cache, c.Resolve()); - }).AsSelf().As>().SingleInstance(); + }).AsSelf().As>().SingleInstance(); _builder.RegisterType().As(); @@ -566,7 +565,7 @@ private void InitContainer(string gameRoot) _builder.RegisterType(); - + _builder.RegisterType(); _builder.RegisterType().As().SingleInstance(); @@ -778,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 => { @@ -846,10 +845,10 @@ private void RegisterEntities() RegisterRobot().OnActivated(e => e.Instance.SetCoreRecharger(e.Context.Resolve())); RegisterRobot(); RegisterRobot(); - + _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); - + RegisterEntity(); RegisterCorporation(); RegisterCorporation(); @@ -865,7 +864,7 @@ private void RegisterEntities() RegisterUnit().OnActivated(e => { #if (DEBUG) - var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromSeconds(10),TimeSpan.FromSeconds(15)); + var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15)); #else var intrusionWaitTime = TimeRange.FromLength(TimeSpan.FromHours(8), TimeSpan.FromHours(8)); #endif @@ -1044,26 +1043,26 @@ private void RegisterEntities() RegisterEntity(); - _builder.Register>(x => + _builder.Register>(x => { var ctx = x.Resolve(); var 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)) { - 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); ByDefinition(ed.Definition, parameters); @@ -1168,34 +1167,34 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_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_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_webber); ByCategoryFlags(CategoryFlags.cf_sensor_dampeners); ByCategoryFlags(CategoryFlags.cf_remote_sensor_boosters); @@ -1220,19 +1219,19 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); @@ -1258,6 +1257,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); @@ -1306,15 +1306,15 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); @@ -1377,7 +1377,7 @@ private void RegisterLoggers() 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")); + return ctx.Resolve.Factory>().Invoke(formatter, () => Path.Combine("chatlogs", directory, filename, DateTime.Now.ToString("yyyy-MM-dd"), $"{filename.RemoveSpecialCharacters()}.txt")); }); }); @@ -1432,8 +1432,8 @@ private void RegisterLoggers() } - private IRegistrationBuilder - RegisterPresence(PresenceType presenceType) where T:Presence + private IRegistrationBuilder + RegisterPresence(PresenceType presenceType) where T : Presence { return _builder.RegisterType().Keyed(presenceType).PropertiesAutowired(); } @@ -1455,7 +1455,7 @@ public void RegisterNpcs() _builder.RegisterType().As(); _builder.RegisterType(); - _builder.RegisterType().Named>("directFlockIDGenerator").SingleInstance().WithParameter("startID",25000); + _builder.RegisterType().Named>("directFlockIDGenerator").SingleInstance().WithParameter("startID", 25000); _builder.RegisterType().OnActivated(e => @@ -1496,7 +1496,7 @@ public void RegisterNpcs() return zone => { var presenceFactory = ctx.Resolve(); - var presenceService = ctx.Resolve().Invoke(zone,presenceFactory); + var presenceService = ctx.Resolve().Invoke(zone, presenceFactory); return presenceService; }; }); @@ -1550,7 +1550,7 @@ public void RegisterNpcs() if (!ctx.IsRegisteredWithKey(configuration.PresenceType)) return null; - var p = ctx.ResolveKeyed(configuration.PresenceType,TypedParameter.From(zone),TypedParameter.From(configuration)); + var p = ctx.ResolveKeyed(configuration.PresenceType, TypedParameter.From(zone), TypedParameter.From(configuration)); if (p is IRoamingPresence roamingPresence) { @@ -1612,7 +1612,7 @@ private void RegisterMissions() } private void InitRelayManager() - { + { _builder.RegisterType().SingleInstance(); _builder.RegisterType().SingleInstance(); @@ -1693,7 +1693,7 @@ private void InitRelayManager() _builder.RegisterType().As(); _builder.RegisterType(); - + _builder.RegisterType().As(); _builder.RegisterType().As(); _builder.RegisterType(); @@ -1755,8 +1755,8 @@ private void InitRelayManager() _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(); @@ -1861,7 +1861,7 @@ private void RegisterRequestHandlers() RegisterRequestHandler(Commands.IntrusionEnabler); RegisterRequestHandler(Commands.AccountGetTransactionHistory); RegisterRequestHandler(Commands.AccountList); - + RegisterRequestHandler(Commands.AccountEpForActivityHistory); RegisterRequestHandler(Commands.RedeemableItemList); RegisterRequestHandler(Commands.RedeemableItemRedeem); @@ -2295,11 +2295,11 @@ private void RegisterTerrains() var config = new GravelConfiguration(zone); var 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 materialLayers = new List(); foreach (var configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) @@ -2308,19 +2308,19 @@ private void RegisterTerrains() switch (configuration.ExtractionType) { case MineralExtractionType.Solid: - { - var layer = new OreLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory, eventListenerService); - layer.LoadMineralNodes(); - materialLayers.Add(layer); - break; - } + { + var 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, eventListenerService); - layer.LoadMineralNodes(); - materialLayers.Add(layer); - break; - } + { + var layer = new LiquidLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory, eventListenerService); + layer.LoadMineralNodes(); + materialLayers.Add(layer); + break; + } default: throw new ArgumentOutOfRangeException(); } @@ -2385,10 +2385,10 @@ private void RegisterTerrains() 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)); + 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))); @@ -2440,7 +2440,7 @@ private void RegisterRifts() _builder.Register>(x => { var ctx = x.Resolve(); - return zone => + return zone => { if (zone is TrainingZone) return null; @@ -2451,7 +2451,7 @@ private void RegisterRifts() var strongHoldExitConfigCount = Db.Query().CommandText("SELECT COUNT(*) FROM strongholdexitconfig WHERE zoneid = @zoneId;") .SetParameter("@zoneId", zone.Id) .ExecuteScalar(); - if (strongHoldExitConfigCount < 1) + if (strongHoldExitConfigCount < 1) { return null; } @@ -2463,7 +2463,7 @@ private void RegisterRifts() var zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") .SetParameter("@zoneId", zone.Id) .Execute(); - if (zoneConfigs.Count < 1) + if (zoneConfigs.Count < 1) { return null; } @@ -2471,14 +2471,14 @@ private void RegisterRifts() var record = zoneConfigs[0]; var maxrifts = record.GetValue("maxrifts"); - if (maxrifts < 1) + if (maxrifts < 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)); + return ctx.Resolve(new TypedParameter(typeof(IZone), zone), new NamedParameter("spawnTime", spawnTime), new NamedParameter("spawnPositionFinder", finder)); }; }); } @@ -2568,8 +2568,8 @@ private void RegisterZones() { 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)) }; }; }); @@ -2579,7 +2579,7 @@ private void RegisterZones() _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 => { @@ -2629,7 +2629,7 @@ void RegisterZone(ZoneType type) where T:Zone 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(); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index f957abf4a..569bf135c 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -669,5 +669,6 @@ public enum CategoryFlags : long cf_zone_objects = 0x0000000000000092, def_pbs_dogtags = 0x0000000000000691, cf_respec_tokens = 0x000000000004049B, + cf_spark_teleport_devices = 0x000000000005049B, } } diff --git a/src/Perpetuum/Items/SparkTeleportDevice.cs b/src/Perpetuum/Items/SparkTeleportDevice.cs new file mode 100644 index 000000000..e123f556b --- /dev/null +++ b/src/Perpetuum/Items/SparkTeleportDevice.cs @@ -0,0 +1,25 @@ +using Perpetuum.Accounting; +using Perpetuum.Accounting.Characters; +using Perpetuum.Common.Loggers.Transaction; +using Perpetuum.Data; +using Perpetuum.Host.Requests; +using Perpetuum.Players; +using Perpetuum.Robots; +using Perpetuum.Services.Sparks.Teleports; +using System; +using System.Collections.Generic; +using System.Transactions; + +namespace Perpetuum.Items +{ + public class SparkTeleportToken : Item + { + public int BaseId + { + get + { + return ED.Options.GetOption("baseId"); + } + } + } +} From 2207073b98b974679c845b0591bdde284fa96000 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Mon, 4 Sep 2023 20:51:37 +0300 Subject: [PATCH 408/457] =?UTF-8?q?=EF=BB=BFSpark=20Teleport=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renaming Spark Teleport Token into Spark Teleport Device --- .../PerpetuumBootstrapper.cs | 3 +- .../UseLotteryItem.cs | 61 ++++++++++++++++--- src/Perpetuum/Items/SparkTeleportDevice.cs | 2 +- src/Perpetuum/Perpetuum.csproj | 1 + 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index df7a26d36..f61021faf 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -920,6 +920,7 @@ private void RegisterEntities() RegisterEntity(); RegisterEntity(); RegisterEntity(); + RegisterEntity(); RegisterEntity(); RegisterEntity(); RegisterEntity(); @@ -1257,7 +1258,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); + ByCategoryFlags(CategoryFlags.cf_spark_teleport_devices); // OPP new Blinder module ByNamePatternAndFlag(DefinitionNames.STANDARD_BLINDER_MODULE, CategoryFlags.cf_target_painter); diff --git a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index 34d3519aa..296a1651e 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Transactions; using Perpetuum.Accounting; using Perpetuum.Accounting.Characters; using Perpetuum.Common.Loggers.Transaction; @@ -10,10 +6,11 @@ using Perpetuum.EntityFramework; using Perpetuum.Host.Requests; using Perpetuum.Items; -using Perpetuum.RequestHandlers.Extensions; using Perpetuum.Robots; -using Perpetuum.Services.ExtensionService; -using Perpetuum.Zones; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Transactions; namespace Perpetuum.RequestHandlers { @@ -58,6 +55,10 @@ public void HandleRequest(IRequest request) { HandleRespecToken(request, itemEid); } + else if (item is SparkTeleportDevice) + { + HandleSparkTeleportDevice(request, itemEid); + } } private void HandleLottery(IRequest request) @@ -291,5 +292,51 @@ private void HandleRespecToken(IRequest request, long itemEid) } }); } + + 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/Items/SparkTeleportDevice.cs b/src/Perpetuum/Items/SparkTeleportDevice.cs index e123f556b..cdedfe94d 100644 --- a/src/Perpetuum/Items/SparkTeleportDevice.cs +++ b/src/Perpetuum/Items/SparkTeleportDevice.cs @@ -12,7 +12,7 @@ namespace Perpetuum.Items { - public class SparkTeleportToken : Item + public class SparkTeleportDevice : Item { public int BaseId { diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index d4547824b..165a32c9f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -226,6 +226,7 @@ + From 1b609aa9689aa82ff0c9f9c5100d07182247285e Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sun, 5 Nov 2023 18:55:18 +0500 Subject: [PATCH 409/457] - Missing changes after rebase --- .../Sparks/SparkTeleportUse.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs index 2d5f52893..6d64e9f20 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 currentDockingBase = character.GetCurrentDockingBase(); 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); From fc5878300b008585713b258b366c760df9d887bd Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Mon, 4 Sep 2023 20:51:37 +0300 Subject: [PATCH 410/457] =?UTF-8?q?=EF=BB=BFSpark=20Teleport=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renaming Spark Teleport Token into Spark Teleport Device --- src/Perpetuum.RequestHandlers/UseLotteryItem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs index 296a1651e..7f0260681 100644 --- a/src/Perpetuum.RequestHandlers/UseLotteryItem.cs +++ b/src/Perpetuum.RequestHandlers/UseLotteryItem.cs @@ -249,7 +249,6 @@ private void HandleRespecToken(IRequest request, long itemEid) (character.IsDocked).ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); var container = Container.GetWithItems(containerEid, character); - var containerItem = (RespecToken)container .GetItemOrThrow(itemEid, true) .Unstack(1); @@ -307,7 +306,6 @@ private void HandleSparkTeleportDevice(IRequest request, long itemEid) (character.IsDocked).ThrowIfFalse(ErrorCodes.CharacterHasToBeDocked); var container = Container.GetWithItems(containerEid, character); - var containerItem = (SparkTeleportDevice)container .GetItemOrThrow(itemEid, true) .Unstack(1); From f552f6115b7fa21b8c11a94587db35a36bd5d0c9 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Tue, 26 Sep 2023 00:52:43 +0300 Subject: [PATCH 411/457] Prototype lines - Advanced and Expert prototype parallel processing are now enabled --- .../Services/ProductionEngine/Facilities/Prototyper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs index 1874c70d8..b9fd22aa8 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) From 7f8fee7b85b99bad414066d54e4c10db9a80b1e1 Mon Sep 17 00:00:00 2001 From: Viktor Dolzhukov Date: Wed, 27 Sep 2023 17:59:01 +0300 Subject: [PATCH 412/457] Gamma corp hangar fix - Added check that Armor and ArmorMax are greater than zero (i.e. initialized) when calculating health --- src/Perpetuum/Robots/Robot.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 8804f83f3..0d9d9776d 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -136,7 +136,9 @@ public override double Health return base.Health; } - return Armor.Ratio(ArmorMax) * 100; + return ArmorMax > 0.0 && Armor > 0.0 + ? Armor.Ratio(ArmorMax) * 100 + : base.Health; } } From 276b975bf45b69effa210e34127f899de96d9ba1 Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:43:19 +0300 Subject: [PATCH 413/457] Update build.yml - project is for .NET Framework, not .NET Core; - changed the path for artifacts to a relative one; - build projects after a push and pull request; - build artifacts after a push. --- .github/workflows/build.yml | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 85110cfc5..da7eaa933 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,20 +12,22 @@ on: 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] - configuration: [Release] + 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 @@ -33,24 +35,15 @@ jobs: with: fetch-depth: 0 - # Install the .NET Core workload - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.0.2 - + uses: microsoft/setup-msbuild@v1.1 # Restore the application to populate the obj folder with RuntimeIdentifiers - - name: Restore the application + - name: Restore&Build the application for (${{ matrix.configuration }}) run: | nuget restore - msbuild $env:Solution_Name -p:Configuration=$env:Configuration - env: - Configuration: ${{ matrix.configuration }} + 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 @@ -58,8 +51,9 @@ jobs: uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && always()}} - - name: Upload a Build Artifact + - name: Upload a Build Artifact for (${{ matrix.configuration }}) uses: actions/upload-artifact@v3 with: - name: Perpetuum-Server - path: D:\a\PerpetuumServer\PerpetuumServer\bin\x64\* + name: Perpetuum-Server-${{ github.sha }}.${{ matrix.configuration }} + path: ${{ env.Workspace }}/bin/x64/${{ env.Configuration }}/Perpetuum.Server + if: ${{ github.event_name == 'push'}} From e575c2aaa92c175f575a39693b1f27befb56e4a8 Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:18:55 +0300 Subject: [PATCH 414/457] Create opp-server.jpeg (#472) - add banner in README.md --- README.md | 2 ++ opp-server.jpeg | Bin 0 -> 278672 bytes 2 files changed, 2 insertions(+) create mode 100644 opp-server.jpeg diff --git a/README.md b/README.md index 35561ce36..8c7d74cf4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![opp-server](opp-server.jpeg) + # The Open Perpetuum Server This is a fork of the Official Perpetuum Server as released by Avatar Creations [here](https://github.com/PerpetuumOnline/PerpetuumServer). This server is developed under the direction of the [Open Perpetuum Project](https://openperpetuum.com) game design and development team; a 100% volunteer opensource player development and persistent Perpetuum Server hosting project. diff --git a/opp-server.jpeg b/opp-server.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..531d18376e0da9927c0ab5a800b4cb3128ed4e7d GIT binary patch literal 278672 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%0D$)ti_B-hS3op#slv*@GDYI4D%2pd_t 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�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%0DlGl05xS}P0Isek^)v@SFuCrFn_qB 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 E**(-?lK=n! literal 0 HcmV?d00001 From e64cb2a673d614edc7d6a64168e6033cf25fbd8f Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sat, 18 Nov 2023 14:43:38 +0500 Subject: [PATCH 415/457] - Added spark teleport changes that were lost during branches rebase --- .../Sparks/SparkTeleportUse.cs | 4 ++-- .../Sparks/Teleports/ISparkTeleportRepository.cs | 4 ++++ .../Services/Sparks/Teleports/SparkTeleportHelper.cs | 5 +++++ .../Sparks/Teleports/SparkTeleportRepository.cs | 12 ++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs index 6d64e9f20..b677520ad 100644 --- a/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs +++ b/src/Perpetuum.RequestHandlers/Sparks/SparkTeleportUse.cs @@ -35,10 +35,10 @@ public void HandleRequest(IRequest request) //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) throw new PerpetuumException(ErrorCodes.YouAreHereAlready); diff --git a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs index 026b1982b..73ce9b15b 100644 --- a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs +++ b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Security.Cryptography; using Perpetuum.Accounting.Characters; using Perpetuum.Units.DockingBases; @@ -6,7 +7,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(); From 3c563ccdc25b67d71a2d872ef5200373ef4e64e1 Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Sat, 18 Nov 2023 13:15:55 +0300 Subject: [PATCH 416/457] Update ISparkTeleportRepository.cs - remove using System.Security.Cryptography. --- .../Services/Sparks/Teleports/ISparkTeleportRepository.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs index 73ce9b15b..fd46cbd48 100644 --- a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs +++ b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Security.Cryptography; using Perpetuum.Accounting.Characters; using Perpetuum.Units.DockingBases; @@ -13,4 +12,4 @@ public interface ISparkTeleportRepository : IRepository IEnumerable GetAllByDockingBase(DockingBase dockingBase); } -} \ No newline at end of file +} From 8ea059d739d7b8b72d20ebeb1c7825c8e25a2704 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sat, 25 Nov 2023 23:02:27 +0500 Subject: [PATCH 417/457] - Module property classes extracted into separated files --- src/Perpetuum/Modules/ActiveModule.cs | 151 +----------------- src/Perpetuum/Modules/ArmorRepairModule.cs | 1 + .../Modules/BlobEmissionModulatorModule.cs | 1 + src/Perpetuum/Modules/DrillerModule.cs | 1 + .../Modules/EffectModules/DetectionModule.cs | 1 + .../EffectModules/MineDetectorModule.cs | 1 + .../RemoteSensorBoosterModule.cs | 1 + .../EffectModules/SensorBoosterModule.cs | 1 + .../EffectModules/SensorDampenerModule.cs | 1 + .../EffectModules/ShieldGeneratorModule.cs | 1 + .../Modules/EffectModules/StealthModule.cs | 1 + .../EffectModules/TargetBlinderModule.cs | 1 + .../EffectModules/TargetPainterModule.cs | 1 + .../Modules/EffectModules/WebberModule.cs | 1 + .../Modules/EnergyDispersionModule.cs | 1 + .../Modules/EnergyNeutralizerModule.cs | 1 + .../Modules/EnergyTransfererModule.cs | 1 + src/Perpetuum/Modules/EnergyVampireModule.cs | 1 + src/Perpetuum/Modules/GangModule.cs | 1 + src/Perpetuum/Modules/HarvesterModule.cs | 1 + src/Perpetuum/Modules/Module.cs | 1 + .../ModuleProperties/CycleTimeProperty.cs | 42 +++++ .../ModuleProperties/FalloffProperty.cs | 41 +++++ .../ModuleProperties/ModuleProperty.cs | 59 +++++++ .../ModuleProperties/OptimalRangeProperty.cs | 45 ++++++ src/Perpetuum/Modules/RemoteController.cs | 1 + src/Perpetuum/Modules/SensorJammerModule.cs | 1 + .../Modules/Weapons/FirearmWeaponModule.cs | 1 + .../Modules/Weapons/MissileWeaponModule.cs | 1 + src/Perpetuum/Modules/Weapons/WeaponModule.cs | 84 ++++++---- src/Perpetuum/Perpetuum.csproj | 4 + .../Scanning/Modules/GeoScannerModule.cs | 1 + 32 files changed, 269 insertions(+), 182 deletions(-) create mode 100644 src/Perpetuum/Modules/ModuleProperties/CycleTimeProperty.cs create mode 100644 src/Perpetuum/Modules/ModuleProperties/FalloffProperty.cs create mode 100644 src/Perpetuum/Modules/ModuleProperties/ModuleProperty.cs create mode 100644 src/Perpetuum/Modules/ModuleProperties/OptimalRangeProperty.cs diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 26c470da5..36d498c0e 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -5,7 +5,7 @@ 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; @@ -17,155 +17,6 @@ namespace Perpetuum.Modules { - 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) - { - 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); - - 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; - } - } - - 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; - } - } - - - 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; - } - } - - public abstract partial class ActiveModule : Module { private Lock _lock; diff --git a/src/Perpetuum/Modules/ArmorRepairModule.cs b/src/Perpetuum/Modules/ArmorRepairModule.cs index dbeb02b57..c456d3c05 100644 --- a/src/Perpetuum/Modules/ArmorRepairModule.cs +++ b/src/Perpetuum/Modules/ArmorRepairModule.cs @@ -1,6 +1,7 @@ using System; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.NpcSystem; diff --git a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs index a79aaee59..354094c76 100644 --- a/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs +++ b/src/Perpetuum/Modules/BlobEmissionModulatorModule.cs @@ -2,6 +2,7 @@ using System.Linq; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones; diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index fa74c99a4..fdef4dc24 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -8,6 +8,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Zones; 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/MineDetectorModule.cs b/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs index df7a98bb3..bc0f9a5e1 100644 --- a/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs +++ b/src/Perpetuum/Modules/EffectModules/MineDetectorModule.cs @@ -1,6 +1,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/RemoteSensorBoosterModule.cs b/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs index 1df2a68b0..15f3557c9 100644 --- a/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs @@ -1,5 +1,6 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.NpcSystem; diff --git a/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs b/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs index aae306ea3..4409d9c25 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs @@ -1,6 +1,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.NpcSystem; diff --git a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs index aa1e5c44f..35186d55d 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -1,6 +1,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.NpcSystem; 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 index fe2a036a5..d90f0df7a 100644 --- a/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs +++ b/src/Perpetuum/Modules/EffectModules/TargetBlinderModule.cs @@ -1,6 +1,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/TargetPainterModule.cs b/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs index be95b5d81..c1c690c87 100644 --- a/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/TargetPainterModule.cs @@ -1,6 +1,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/WebberModule.cs b/src/Perpetuum/Modules/EffectModules/WebberModule.cs index 7ebabc701..952d29a7b 100644 --- a/src/Perpetuum/Modules/EffectModules/WebberModule.cs +++ b/src/Perpetuum/Modules/EffectModules/WebberModule.cs @@ -1,6 +1,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.NpcSystem; 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 72fd313e2..41212cebd 100644 --- a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs +++ b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs @@ -2,6 +2,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; diff --git a/src/Perpetuum/Modules/EnergyTransfererModule.cs b/src/Perpetuum/Modules/EnergyTransfererModule.cs index 698f8a5b0..96b3dbfb5 100644 --- a/src/Perpetuum/Modules/EnergyTransfererModule.cs +++ b/src/Perpetuum/Modules/EnergyTransfererModule.cs @@ -1,6 +1,7 @@ using System; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; diff --git a/src/Perpetuum/Modules/EnergyVampireModule.cs b/src/Perpetuum/Modules/EnergyVampireModule.cs index 3c75cdf2f..813910d24 100644 --- a/src/Perpetuum/Modules/EnergyVampireModule.cs +++ b/src/Perpetuum/Modules/EnergyVampireModule.cs @@ -2,6 +2,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; 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/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index a04d3f54f..0988dcde4 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -6,6 +6,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Services.MissionEngine.MissionTargets; using Perpetuum.Zones; diff --git a/src/Perpetuum/Modules/Module.cs b/src/Perpetuum/Modules/Module.cs index 2ab0d74b0..bdbad69fb 100644 --- a/src/Perpetuum/Modules/Module.cs +++ b/src/Perpetuum/Modules/Module.cs @@ -2,6 +2,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Robots; using Perpetuum.Zones; 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/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/RemoteController.cs b/src/Perpetuum/Modules/RemoteController.cs index 6e8a1a7fa..61897b662 100644 --- a/src/Perpetuum/Modules/RemoteController.cs +++ b/src/Perpetuum/Modules/RemoteController.cs @@ -1,5 +1,6 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; diff --git a/src/Perpetuum/Modules/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index 27791f5f9..829080d1e 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -1,5 +1,6 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Robots; using Perpetuum.Units; using Perpetuum.Zones; diff --git a/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs index 8f05e7ff7..04ae4b9b3 100644 --- a/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/FirearmWeaponModule.cs @@ -1,4 +1,5 @@ using Perpetuum.ExportedTypes; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; namespace Perpetuum.Modules.Weapons diff --git a/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs b/src/Perpetuum/Modules/Weapons/MissileWeaponModule.cs index 71c2a092d..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 diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index f821e96e9..7fa3eb8bf 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Services.RiftSystem; +using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -39,7 +39,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) + { + 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]; } private class ModuleAction : ILockVisitor @@ -56,6 +87,7 @@ public void DoAction() _weapon.ParentRobot.HasShieldEffect.ThrowIfTrue(ErrorCodes.ShieldIsActive); var currentLock = _weapon.GetLock(); + currentLock?.AcceptVisitor(this); } @@ -72,18 +104,22 @@ public void VisitUnitLock(UnitLock unitLock) victim.States.Dead.ThrowIfTrue(ErrorCodes.TargetIsDead); var err = victim.IsAttackable; + if (err != ErrorCodes.NoError) + { throw new PerpetuumException(err); + } victim.IsInvulnerable.ThrowIfTrue(ErrorCodes.TargetIsInvulnerable); - _weapon.ConsumeAmmo(); var result = _weapon.GetLineOfSight(victim); + if (result.hit) { DoDamageToPosition(result.position); _weapon.OnError(ErrorCodes.LOSFailed); + return; } @@ -91,19 +127,22 @@ public void VisitUnitLock(UnitLock unitLock) 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); + 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); + flyTime += delay; var builder = _weapon.GetDamageBuilder(); + Task.Delay(flyTime).ContinueWith(t => victim.TakeDamage(builder.Build())); } @@ -114,9 +153,11 @@ public void VisitTerrainLock(TerrainLock terrainLock) _weapon.ConsumeAmmo(); var blockingInfo = _weapon?.ParentRobot?.Zone?.Terrain.Blocks.GetValue(terrainLock.Location) ?? BlockingInfo.None; - location = location.AddToZ(Math.Min(blockingInfo.Height,20)); + + location = location.AddToZ(Math.Min(blockingInfo.Height, 20)); var losResult = _weapon.GetLineOfSight(location); + if (losResult.hit && !location.IsEqual2D(losResult.position)) { location = losResult.position; @@ -131,18 +172,23 @@ private void DoDamageToPosition(Position location) var distance = _weapon.ParentRobot.CurrentPosition.TotalDistance3D(location); var bulletTime = _weapon.GetAmmo().BulletTime; var flyTime = (int)((distance / bulletTime) * 1000); - var beamTime = (int)Math.Max(flyTime, _weapon.CycleTime.TotalMilliseconds); + flyTime += _weapon.CreateBeam(location, BeamState.Hit, beamTime, bulletTime); var damage = _weapon.GetDamageBuilder().Build().CalculatePlantDamages(); if (damage <= 0.0) + { return; + } var zone = _weapon.Zone; + if (zone == null) + { return; + } Task.Delay(flyTime).ContinueWith(t => DealDamageToPosition(zone, location, damage)); } @@ -155,31 +201,5 @@ private static void DealDamageToPosition(IZone zone, Position location, double d } } } - - 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 6a1075f3d..01d2fa5a1 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -260,6 +260,10 @@ + + + + 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; From 63efea6e1d99d2ba325a1162a658f262317ff6ab Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sun, 3 Dec 2023 01:00:08 +0500 Subject: [PATCH 418/457] - There is now just machine gun turret of 4 tiers with enabler extensions (2 lvl per tier) - Turrets are now being removed once Spectator leaves the zone (either dock, dead or teleported out) - Fixed incorrect free bandwidth calculation (now up to 5 turrets as planned) - Minor refactoring and styling --- .../PerpetuumBootstrapper.cs | 1 + src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1 + ...ontroller.cs => RemoteControllerModule.cs} | 23 +- src/Perpetuum/Modules/Weapons/WeaponAmmo.cs | 8 +- src/Perpetuum/Perpetuum.csproj | 3 +- src/Perpetuum/Robots/Robot.cs | 391 +++++++++--------- .../Robots/RobotInfoPacketBuilder.cs | 40 ++ src/Perpetuum/Zones/NpcSystem/Turret.cs | 181 +++++--- .../Zones/RemoteControl/BandwidthHandler.cs | 13 +- 9 files changed, 406 insertions(+), 255 deletions(-) rename src/Perpetuum/Modules/{RemoteController.cs => RemoteControllerModule.cs} (89%) create mode 100644 src/Perpetuum/Robots/RobotInfoPacketBuilder.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 4cd2cc73c..836f360c1 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -1182,6 +1182,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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)); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 254b32b00..94b0e84a7 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -260,6 +260,7 @@ public enum CategoryFlags : long cf_medium_industrial_aoe = 0x00000000020A070F, cf_large_industrial_aoe = 0x00000000030A070F, cf_remote_controllers = 0x00000000000B070F, + cf_sentry_turret_guns = 0x00000000000C070F, cf_terraforming_equipment = 0x000000000000080F, cf_small_terraformer = 0x000000000001080F, cf_terraforming_digger = 0x000000000002080F, diff --git a/src/Perpetuum/Modules/RemoteController.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs similarity index 89% rename from src/Perpetuum/Modules/RemoteController.cs rename to src/Perpetuum/Modules/RemoteControllerModule.cs index 61897b662..7cbec0792 100644 --- a/src/Perpetuum/Modules/RemoteController.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -1,6 +1,8 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Items; using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; @@ -87,6 +89,11 @@ public override void AcceptVisitor(IEntityVisitor visitor) } } + public void CloseAllChannels() + { + bandwidthHandler.CloseAllChannels(); + } + protected override void OnAction() { if (bandwidthHandler == null) @@ -139,6 +146,12 @@ protected override void OnAction() } var ammo = GetAmmo() as RemoteControlledUnit; + + if (this.ParentRobot is Player player) + { + ammo.CheckEnablerExtensionsAndThrowIfFailed(player.Character, ErrorCodes.ExtensionLevelMismatchTerrain); + } + var fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); fieldTurret.Owner = this.ParentRobot.Eid; @@ -148,7 +161,9 @@ protected override void OnAction() var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); - fieldTurret.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); + var despawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); + + fieldTurret.DespawnTime = despawnTime; fieldTurret.SetGroup(bandwidthHandler); @@ -164,7 +179,11 @@ protected override void OnAction() var effectBuilder = this.ParentRobot.NewEffectBuilder(); SetupEffect(effectBuilder); - effectBuilder.WithToken(effectToken); + + effectBuilder + .WithToken(effectToken) + .WithDuration(despawnTime); + this.ParentRobot.ApplyEffect(effectBuilder); ConsumeAmmo(); diff --git a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs index b09810464..cdfd2ba87 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponAmmo.cs @@ -54,9 +54,7 @@ private IList CalculateCleanDamages() } } - var damageModifier = weapon.ParentRobot is SentryTurret - ? weapon.RemoteControlDamageModifier.ToPropertyModifier() - : weapon.DamageModifier.ToPropertyModifier(); + var damageModifier = weapon.DamageModifier.ToPropertyModifier(); var property = GetPropertyModifier(AggregateField.damage_chemical); @@ -84,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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 01d2fa5a1..7c76b7a1a 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -265,7 +265,7 @@ - + @@ -295,6 +295,7 @@ + diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index ff91983da..8d3a06098 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -8,7 +8,6 @@ using Perpetuum.Containers.SystemContainers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; - using Perpetuum.Items; using Perpetuum.Items.Templates; using Perpetuum.Modules; @@ -19,179 +18,194 @@ using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.RemoteControl; 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) + public RobotHelper RobotHelper { protected get; set; } + public InsuranceHelper InsuranceHelper { protected get; set; } + + public RobotTemplate Template { get; set; } + + public bool IsSelected => RobotHelper.IsSelected(this); + + public override double Health { - _robot = robot; - _command = command; + get + { + if (IsRepackaged) + { + return base.Health; + } + + return ArmorMax > 0.0 && Armor > 0.0 + ? Armor.Ratio(ArmorMax) * 100 + : base.Health; + } } - public Packet Build() + public override double Mass { - var infoPacket = new Packet(_command); - infoPacket.AppendLong(_robot.Eid); - AppendRobotProperties(infoPacket); - return infoPacket; + get { return RobotComponents.Sum(c => c.Mass); } } - private void AppendRobotProperties(Packet infoPacket) + public bool HasModule { - var properties = _robot.Properties.Where(p => p.HasValue).ToArray(); - - infoPacket.AppendInt(properties.Length); + get { return Modules.Any(); } + } - foreach (var property in properties) + public bool IsItemsInContainer + { + get { - infoPacket.AppendProperty(property); + if (IsRepackaged) + { + return false; + } + + var robotInventory = GetContainer(); + + if (robotInventory == null) + { + return false; + } + + return robotInventory.HasChildren; } } - } - public abstract partial class Robot : Unit - { - protected Robot() + public override bool IsStackable { - InitLockHander(); - InitProperties(); + get { return base.IsStackable && IsRepackaged; } } - public RobotHelper RobotHelper { protected get; set; } - public InsuranceHelper InsuranceHelper { protected get; set; } - - public RobotTemplate Template { get; set; } - - public override void Initialize() + public IEnumerable ExtensionBonusEnablerExtensions { - InitComponents(); - base.Initialize(); + get { return ExtensionBonuses.Select(cb => new Extension(cb.extensionId, 1)); } } - private Lazy> _modules; - private Lazy> _activeModules; - private Lazy> _components; - private Lazy> _robotComponents; + protected IEnumerable ExtensionBonuses + { + get { return RobotComponents.SelectMany(component => component.ExtensionBonuses); } + } - private void InitComponents() + public IEnumerable Modules { - _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()); + get { return _modules.Value; } } - protected virtual void OnLockError(Lock @lock, ErrorCodes error) + public IEnumerable ActiveModules { + get { return _activeModules.Value; } } - public override void AcceptVisitor(IEntityVisitor visitor) + public IEnumerable Components { - if (!TryAcceptVisitor(this, visitor)) - base.AcceptVisitor(visitor); + get { return _components.Value; } } - public void VisitModules(IEntityVisitor visitor) + public IEnumerable RobotComponents { - foreach (var module in Modules) - { - module.AcceptVisitor(visitor); - } + get { return _robotComponents.Value; } } - public void VisitRobotComponents(IEntityVisitor visitor) + public override double Volume { - foreach (var component in RobotComponents) + get { - component.AcceptVisitor(visitor); + if (IsRepackaged) + { + return base.Volume; + } + + var volume = RobotComponents.Sum(c => c.Volume); + + volume *= Quantity; + + return volume; } } - public void VisitRobotInventory(IEntityVisitor visitor) + public bool IsTrashed { - var container = GetContainer(); - container?.AcceptVisitor(visitor); + get { return Trashcan.IsItemTrashed(this); } } - public override IDictionary GetDebugInfo() + protected Robot() { - var info = base.GetDebugInfo(); - info.Add("locksCount", _lockHandler.Count); - return info; + InitLockHander(); + InitProperties(); } - public bool IsSelected => RobotHelper.IsSelected(this); + public override void Initialize() + { + InitComponents(); + base.Initialize(); + } - public override double Health + public override void AcceptVisitor(IEntityVisitor visitor) { - get + if (!TryAcceptVisitor(this, visitor)) { - if (IsRepackaged) - { - return base.Health; - } - - return ArmorMax > 0.0 && Armor > 0.0 - ? Armor.Ratio(ArmorMax) * 100 - : base.Health; + base.AcceptVisitor(visitor); } } - public override double Mass + public void VisitModules(IEntityVisitor visitor) { - get { return RobotComponents.Sum(c => c.Mass); } + foreach (var module in Modules) + { + module.AcceptVisitor(visitor); + } } - protected virtual void OnLockStateChanged(Lock @lock) + public void VisitRobotComponents(IEntityVisitor visitor) { - States.LockSomething = _lockHandler.Count > 0; - - var unitLock = @lock as UnitLock; - if (unitLock != null) + foreach (var component in RobotComponents) { - UpdateTypes |= UnitUpdateTypes.Lock; - UpdateVisibilityOf(unitLock.Target); + component.AcceptVisitor(visitor); } + } - var builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); - OnBroadcastPacket(builder.ToProxy()); + public void VisitRobotInventory(IEntityVisitor visitor) + { + var container = GetContainer(); + + container?.AcceptVisitor(visitor); } - protected override void OnUpdate(TimeSpan time) + public override IDictionary GetDebugInfo() { - base.OnUpdate(time); + var info = base.GetDebugInfo(); - _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; if (currentCore >= coreMaxValue) + { return; + } - DynamicProperties.Update(k.currentCore,coreMaxValue); + DynamicProperties.Update(k.currentCore, coreMaxValue); Core = CoreMax; } @@ -201,31 +215,6 @@ 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) @@ -234,31 +223,13 @@ public void StopAllModules() } } - protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) - { - base.OnDamageTaken(source, e); - - var decayChance = this.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) { @@ -266,15 +237,13 @@ public void EmptyRobot(Character character,Container targetContainer,bool withCo } if (!withContainer) + { return; + } var container = GetContainer(); - container?.RelocateItems(character, character, container.GetItems(), targetContainer); - } - public override bool IsStackable - { - get { return base.IsStackable && IsRepackaged; } + container?.RelocateItems(character, character, container.GetItems(), targetContainer); } public override Dictionary ToDictionary() @@ -288,24 +257,15 @@ public override Dictionary ToDictionary() var 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] @@ -338,42 +298,30 @@ public RobotComponent GetRobotComponent(RobotComponentType componentType) return RobotComponents.FirstOrDefault(c => c.Type == componentType); } - public IEnumerable Modules - { - get { return _modules.Value; } - } - - public IEnumerable ActiveModules - { - get { return _activeModules.Value; } - } - - public IEnumerable Components - { - get { return _components.Value; } - } - - public IEnumerable RobotComponents - { - get { return _robotComponents.Value; } - } - - public void CheckEnergySystemAndThrowIfFailed(Module module, bool isRemoving=false) + public void CheckEnergySystemAndThrowIfFailed(Module module, bool isRemoving = false) { if (!CheckPowerGridForModule(module, isRemoving)) + { throw PerpetuumException.Create(ErrorCodes.OutOfPowergrid); + } 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() @@ -384,36 +332,97 @@ public void CreateComponents() } } + [CanBeNull] + public T GetRobotComponent() where T : RobotComponent + { + return RobotComponents.OfType().FirstOrDefault(); + } + + protected virtual void OnLockError(Lock @lock, ErrorCodes error) + { + } + + 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); + } + + var builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); + + OnBroadcastPacket(builder.ToProxy()); + } + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + + _lockHandler.Update(time); + + foreach (var robotComponent in RobotComponents) + { + robotComponent.Update(time); + } + } + + protected internal override double ComputeHeight() + { + return RobotComponents.Sum(c => c.ComputeHeight()); + } + + protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) + { + base.OnDamageTaken(source, e); + + var decayChance = this.decayChance.Value; + var random = FastRandom.NextDouble(); + + if (decayChance >= random) + { + var d = Decay; + + if (d > 0) + { + d--; + Decay = d; + } + } + } + protected override bool IsDetected(Unit target) { if (_lockHandler.IsLocked(target)) + { return true; + } return base.IsDetected(target); } - public override double Volume + protected override void OnRemovedFromZone(IZone zone) { - get - { - if (IsRepackaged) - return base.Volume; + var remoteController = _modules.Value.FirstOrDefault(x => x is RemoteControllerModule) ; - var volume = RobotComponents.Sum(c => c.Volume); - volume *= Quantity; - return volume; + if (remoteController != null) + { + (remoteController as RemoteControllerModule).CloseAllChannels(); } - } - public bool IsTrashed - { - get { return Trashcan.IsItemTrashed(this); } + base.OnRemovedFromZone(zone); } - [CanBeNull] - public T GetRobotComponent() where T : RobotComponent + private void InitComponents() { - return RobotComponents.OfType().FirstOrDefault(); + _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/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/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index 0bb57a2d4..b17cc281c 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -16,22 +16,36 @@ namespace Perpetuum.Zones.NpcSystem { - public class ModuleActivator : IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor + public class ModuleActivator : + 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; @@ -46,226 +60,272 @@ 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.95; - private const double ARMOR_REPAIR_CORE_THRESHOLD = 0.35; - 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); } - private const double SHIELD_ARMOR_THRESHOLD = 0.35; - 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.55; - 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.55; 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.55; - 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 PAINTER_CORE_THRESHOLD = 0.65; - public void Visit(TargetPainterModule module) { if (module.ParentRobot.CorePercentage < PAINTER_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 BLOBBER_CORE_THRESHOLD = 0.55; - public void Visit(BlobEmissionModulatorModule module) { if (module.ParentRobot.Zone.Configuration.Protected) + { return; + } if (module.ParentRobot.CorePercentage < BLOBBER_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 BLINDER_CORE_THRESHOLD = 0.55; - public void Visit(TargetBlinderModule module) { if (module.ParentRobot.CorePercentage < BLINDER_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.55; - public void Visit(EnergyNeutralizerModule module) { if (module.ParentRobot.CorePercentage < ENERGY_NEUTRALIZER_CORE_THRESHOLD) + { return; + } var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); - if ( lockTarget == null ) + + 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 != null && r.hit) { - if (r.hit) - return; + return; } module.Lock = lockTarget; module.State.SwitchTo(ModuleStateType.Oneshot); } - private const double ENERGY_VAMPIRE_CORE_THRESHOLD = 0.05; - 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 != null && r.hit) { - if (r.hit) - return; + return; } module.Lock = lockTarget; @@ -288,15 +348,24 @@ public void Visit(ArmorHardenerModule module) } } - private const double ENERGY_INJECTOR_THRESHOLD = 0.65; - public void Visit(CoreBoosterModule module) { if (module.ParentRobot.CorePercentage > ENERGY_INJECTOR_THRESHOLD) + { 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/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index fba89e210..1f5d2d559 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -44,7 +44,7 @@ public List Turrets public bool HasFreeBandwidthFor(RemoteControlledUnit unit) { - return BandwidthUsed < owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; + return BandwidthUsed <= owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; } public void UseRemoteChannel(SentryTurret turret) @@ -71,6 +71,17 @@ public void OnRemoteChannelDeactivated(SentryTurret turret) Update(); } + public void CloseAllChannels() + { + foreach (var channel in channels) + { + channel.Turret.RemoveFromZone(); + ReleaseRemoteChannel(channel); + } + + Update(); + } + private void ReleaseRemoteChannel(RemoteChannel channel) { deactivatedChannels.Enqueue(channel); From 11a1afa29da46259fb817e86fe5725227be97743 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sun, 3 Dec 2023 13:51:07 +0500 Subject: [PATCH 419/457] - Minog refactoring and style guide before further develpoment --- src/Perpetuum/Modules/ArmorRepairModule.cs | 16 +- .../RemoteSensorBoosterModule.cs | 13 +- .../EffectModules/SensorBoosterModule.cs | 11 +- .../EffectModules/SensorDampenerModule.cs | 12 +- .../Modules/EffectModules/WebberModule.cs | 17 +- .../Modules/EnergyNeutralizerModule.cs | 10 +- .../Modules/EnergyTransfererModule.cs | 7 +- src/Perpetuum/Modules/EnergyVampireModule.cs | 11 +- .../Modules/RemoteArmorRepairModule.cs | 5 +- src/Perpetuum/Modules/SensorJammerModule.cs | 8 +- src/Perpetuum/Perpetuum.csproj | 12 +- src/Perpetuum/Units/UnitExtensions.cs | 4 +- .../Zones/Locking/Locks/TerraformDirection.cs | 9 + .../Zones/Locking/Locks/TerrainLock.cs | 47 +-- .../TerrainLockParametersPacketBuilder.cs | 27 ++ .../NpcSystem/AI/BodyPullThreatHelper.cs | 1 + src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 1 + .../Zones/NpcSystem/SmartCreature.cs | 1 + .../NpcSystem/ThreatManagement/Hostile.cs | 82 +++++ .../ThreatManagement/IPseudoThreatManager.cs | 20 ++ .../ThreatManagement/IThreatManager.cs | 18 + .../ThreatManagement/PseudoThreat.cs | 37 ++ .../ThreatManagement/PseudoThreatManager.cs | 80 +++++ .../NpcSystem/ThreatManagement/Threat.cs | 33 ++ .../ThreatManagement/ThreatExtensions.cs | 11 + .../ThreatManagement/ThreatManager.cs | 72 ++++ .../NpcSystem/ThreatManagement/ThreatType.cs | 16 + .../Zones/NpcSystem/ThreatManager.cs | 315 ------------------ 28 files changed, 511 insertions(+), 385 deletions(-) create mode 100644 src/Perpetuum/Zones/Locking/Locks/TerraformDirection.cs create mode 100644 src/Perpetuum/Zones/Locking/Locks/TerrainLockParametersPacketBuilder.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/IPseudoThreatManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/IThreatManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreat.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/Threat.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatExtensions.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatType.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/ThreatManager.cs diff --git a/src/Perpetuum/Modules/ArmorRepairModule.cs b/src/Perpetuum/Modules/ArmorRepairModule.cs index c456d3c05..cbd32bace 100644 --- a/src/Perpetuum/Modules/ArmorRepairModule.cs +++ b/src/Perpetuum/Modules/ArmorRepairModule.cs @@ -4,7 +4,7 @@ using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -28,6 +28,7 @@ public override void UpdateProperty(AggregateField field) case AggregateField.effect_repair_amount_modifier: { armorRepairAmount.Update(); + return; } } @@ -37,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); @@ -60,14 +65,19 @@ 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); + var threatAmount = Math.Sqrt(amount).Clamp(1, 100); + ParentRobot.SpreadAssistThreatToNpcs(ParentRobot, new Threat(ThreatType.Support, threatAmount)); } } diff --git a/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs b/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs index 15f3557c9..cf500cc5e 100644 --- a/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/RemoteSensorBoosterModule.cs @@ -4,6 +4,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Effects; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -23,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; } @@ -36,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 4409d9c25..6d37943bc 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorBoosterModule.cs @@ -4,7 +4,7 @@ using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -24,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 35186d55d..1c4589c32 100644 --- a/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs +++ b/src/Perpetuum/Modules/EffectModules/SensorDampenerModule.cs @@ -4,7 +4,7 @@ using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; using Perpetuum.Zones.Effects; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules.EffectModules { @@ -17,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); @@ -29,18 +28,23 @@ public SensorDampenerModule() : base(true) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override bool CanApplyEffect(Unit target) { var targetSensorStrength = target.SensorStrength * FastRandom.NextDouble(); var rangedEcmStrength = ModifyValueByOptimalRange(target, _ecmStrength.Value); + if (targetSensorStrength < rangedEcmStrength) { return true; } + OnError(ErrorCodes.AccuracyCheckFailed); + return false; } @@ -51,7 +55,9 @@ protected override void OnApplyingEffect(Unit target) protected override void SetupEffect(EffectBuilder effectBuilder) { - effectBuilder.SetType(EffectType.effect_sensor_supress).SetSource(ParentRobot) + effectBuilder + .SetType(EffectType.effect_sensor_supress) + .SetSource(ParentRobot) .WithPropertyModifier(_effectSensorDampenerLockingRangeModifier.ToPropertyModifier()) .WithPropertyModifier(_effectSensorDampenerLockingTimeModifier.ToPropertyModifier()); } diff --git a/src/Perpetuum/Modules/EffectModules/WebberModule.cs b/src/Perpetuum/Modules/EffectModules/WebberModule.cs index 952d29a7b..ec0046cf7 100644 --- a/src/Perpetuum/Modules/EffectModules/WebberModule.cs +++ b/src/Perpetuum/Modules/EffectModules/WebberModule.cs @@ -4,7 +4,7 @@ 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,7 +22,9 @@ public WebberModule() : base(true) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override bool CanApplyEffect(Unit target) @@ -30,17 +32,24 @@ 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; } @@ -52,12 +61,16 @@ 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) + effectBuilder + .SetType(EffectType.effect_demobilizer) .SetSource(ParentRobot) .WithPropertyModifier(effectProperty); } diff --git a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs index 41212cebd..2a3f85690 100644 --- a/src/Perpetuum/Modules/EnergyNeutralizerModule.cs +++ b/src/Perpetuum/Modules/EnergyNeutralizerModule.cs @@ -6,7 +6,7 @@ using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -23,7 +23,9 @@ public EnergyNeutralizerModule() public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void OnAction() @@ -33,6 +35,7 @@ protected override void OnAction() if (!LOSCheckAndCreateBeam(unitLock.Target)) { OnError(ErrorCodes.LOSFailed); + return; } @@ -45,15 +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)); + 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 96b3dbfb5..df1564673 100644 --- a/src/Perpetuum/Modules/EnergyTransfererModule.cs +++ b/src/Perpetuum/Modules/EnergyTransfererModule.cs @@ -6,6 +6,7 @@ using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -28,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; @@ -40,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 813910d24..9875dd689 100644 --- a/src/Perpetuum/Modules/EnergyVampireModule.cs +++ b/src/Perpetuum/Modules/EnergyVampireModule.cs @@ -6,7 +6,7 @@ using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -23,7 +23,9 @@ public EnergyVampireModule() public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } protected override void OnAction() @@ -33,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); @@ -48,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.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/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/SensorJammerModule.cs b/src/Perpetuum/Modules/SensorJammerModule.cs index 829080d1e..3b0c2c15c 100644 --- a/src/Perpetuum/Modules/SensorJammerModule.cs +++ b/src/Perpetuum/Modules/SensorJammerModule.cs @@ -6,7 +6,7 @@ using Perpetuum.Zones; using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; namespace Perpetuum.Modules { @@ -24,17 +24,19 @@ public SensorJammerModule() : base(true) public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } 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(); var rangedEcmStrength = ModifyValueByOptimalRange(robot, _ecmStrength.Value); + if (targetSensorStrength < rangedEcmStrength) { robot.ResetLocks(); @@ -43,9 +45,9 @@ protected override void OnAction() } 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/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 7c76b7a1a..42d3f453f 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -660,6 +660,8 @@ + + @@ -684,6 +686,15 @@ + + + + + + + + + @@ -1069,7 +1080,6 @@ - diff --git a/src/Perpetuum/Units/UnitExtensions.cs b/src/Perpetuum/Units/UnitExtensions.cs index 48f0a8811..74c5a6cba 100644 --- a/src/Perpetuum/Units/UnitExtensions.cs +++ b/src/Perpetuum/Units/UnitExtensions.cs @@ -4,6 +4,7 @@ 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; @@ -26,9 +27,6 @@ public static void AddDirectThreat(this Unit unit, Unit hostile, double threatVa 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); 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/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index 9a84384d8..977e9b894 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -3,6 +3,7 @@ using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using System.Linq; namespace Perpetuum.Zones.NpcSystem.AI diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index 7687ec222..214af8f81 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -2,6 +2,7 @@ using Perpetuum.Timers; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using Perpetuum.Zones.Terrains; using System; using System.Collections.Generic; diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 56daf10a6..169579d94 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -9,6 +9,7 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem.AI; using Perpetuum.Zones.NpcSystem.Flocks; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using Perpetuum.Zones.RemoteControl; using System; using System.Linq; diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs new file mode 100644 index 000000000..fdecf1612 --- /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..25e06b1c5 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs @@ -0,0 +1,80 @@ +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 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..9ee3345e2 --- /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 9b9fe7e65..000000000 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManager.cs +++ /dev/null @@ -1,315 +0,0 @@ -using Perpetuum.Comparers; -using Perpetuum.Timers; -using Perpetuum.Units; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; - -namespace Perpetuum.Zones.NpcSystem -{ - public enum ThreatType - { - Undefined, - Bodypull, - Damage, - Support, - Lock, - Buff, - Debuff, - Direct, - EnWar, - Ewar - } - - 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 IsThreatened { get; } - - bool Contains(Unit hostile); - - void Remove(Hostile hostile); - - ImmutableSortedSet Hostiles { get; } - - void Clear(); - } - - public static class ThreatExtensions - { - [CanBeNull] - public static Hostile GetMostHatedHostile(this IThreatManager manager) - { - return manager.Hostiles.Min; - } - } - - public class ThreatManager : IThreatManager - { - private ImmutableDictionary _hostiles = ImmutableDictionary.Empty; - - public Hostile GetOrAddHostile(Unit unit) - { - return ImmutableInterlocked.GetOrAdd(ref _hostiles, unit.Eid, eid => - { - var h = new Hostile(unit); - return h; - }); - } - - public ImmutableSortedSet Hostiles - { - get { return _hostiles.Values.ToImmutableSortedSet(); } - } - - public bool IsThreatened - { - get { return !_hostiles.IsEmpty; } - } - - 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(); - } - } - - /// - /// 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); - } - - - public class PseudoThreatManager : IPseudoThreatManager - { - private readonly List _pseudoThreats; - private readonly object _lock; - - public PseudoThreatManager() - { - _pseudoThreats = new List(); - _lock = new object(); - } - - 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); - } - } - - - /// - /// 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 PseudoThreat(Unit unit) - { - Unit = unit; - } - - public Unit Unit { get; } - - public bool IsExpired - { - get { return _lastUpdated > Expiration; } - } - - public void RefreshThreat() - { - _lastUpdated = TimeSpan.Zero; - } - - public void Update(TimeSpan time) - { - _lastUpdated += time; - } - } -} From dab2710dbaea55aaf17d235b64d6479173ea9594 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Mon, 4 Dec 2023 13:20:03 +0500 Subject: [PATCH 420/457] - Mining turret vAlpha --- .../PerpetuumBootstrapper.cs | 8 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 3 + .../EntityFramework/EntityDefaultOptions.cs | 12 + src/Perpetuum/Modules/ActiveModule.cs | 2 +- src/Perpetuum/Modules/DrillerModule.cs | 135 +++++---- .../MiningAmountModifierProperty.cs | 31 ++ .../Modules/RemoteControlledDrillerModule.cs | 126 ++++++++ .../Modules/RemoteControllerModule.cs | 14 +- src/Perpetuum/Perpetuum.csproj | 30 +- src/Perpetuum/Robots/Robot.Locking.cs | 16 + .../NpcReinforcementSpawner.cs | 2 +- src/Perpetuum/Units/Unit.cs | 2 + src/Perpetuum/Zones/Locking/LockHandler.cs | 286 +++++++++--------- .../UnitProperties/LockingTimeProperty.cs | 27 ++ .../MaxLockedTargetsProperty.cs | 31 ++ .../MaxTargetingRangeProperty.cs | 27 ++ .../Zones/NpcSystem/AI/AggressorAI.cs | 12 +- .../NpcSystem/AI/BodyPullThreatHelper.cs | 4 +- src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 35 +-- .../Zones/NpcSystem/AI/IndustrialAI.cs | 253 ++++++++++++++++ .../Zones/NpcSystem/AI/IndustrialTurretAI.cs | 43 +++ .../NpcSystem/AI/MiningTurretIndustrialAI.cs | 16 + .../NpcSystem/AI/SentryTurretCombatAI.cs | 17 +- .../Zones/NpcSystem/AI/StationaryCombatAI.cs | 31 +- .../NpcSystem/AI/StationaryIndustrialAI.cs | 35 +++ src/Perpetuum/Zones/NpcSystem/Creature.cs | 32 ++ .../IIndustrialValueManager.cs | 17 ++ .../IndustrialTarget.cs | 83 +++++ .../IndustrialValue.cs | 28 ++ .../IndustrialValueExtensions.cs | 11 + .../IndustrialValueManager.cs | 73 +++++ .../IndustrialValueType.cs | 9 + src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs | 2 +- src/Perpetuum/Zones/NpcSystem/Npc.cs | 11 +- .../PrimaryLockSelectionStrategyBuilder.cs | 26 -- .../PrimaryLockSelectionStrategySelector.cs | 27 -- .../Zones/NpcSystem/SmartCreature.cs | 92 +++++- .../CombatPrimaryLockSelectionStrategy.cs} | 4 +- ...mbatPrimaryLockSelectionStrategyBuilder.cs | 26 ++ ...batPrimaryLockSelectionStrategySelector.cs | 27 ++ .../CombatStrategies.cs} | 28 +- .../IndustrialPrimaryLockSelectionStrategy.cs | 7 + ...rialPrimaryLockSelectionStrategyBuilder.cs | 26 ++ ...ialPrimaryLockSelectionStrategySelector.cs | 27 ++ .../IndustrialStrategies.cs | 58 ++++ .../NpcSystem/ThreatManagement/Hostile.cs | 4 +- .../ThreatManagement/ThreatManager.cs | 4 +- src/Perpetuum/Zones/NpcSystem/Turret.cs | 16 +- .../Zones/RemoteControl/BandwidthHandler.cs | 4 +- .../Zones/RemoteControl/MiningTurret.cs | 115 +++++++ .../Zones/RemoteControl/RemoteChannel.cs | 35 ++- .../RemoteControl/RemoteControlledTurret.cs | 65 ++++ .../Zones/RemoteControl/SentryTurret.cs | 57 +--- .../Zones/RemoteControl/TurretType.cs | 8 + .../Zones/Scanning/Scanners/Scanner.Tile.cs | 3 +- .../Zones/Terrains/TerrainExtensions.cs | 15 + 56 files changed, 1709 insertions(+), 429 deletions(-) create mode 100644 src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs create mode 100644 src/Perpetuum/Modules/RemoteControlledDrillerModule.cs create mode 100644 src/Perpetuum/Zones/Locking/UnitProperties/LockingTimeProperty.cs create mode 100644 src/Perpetuum/Zones/Locking/UnitProperties/MaxLockedTargetsProperty.cs create mode 100644 src/Perpetuum/Zones/Locking/UnitProperties/MaxTargetingRangeProperty.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/MiningTurretIndustrialAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IIndustrialValueManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialTarget.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValue.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueExtensions.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueManager.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/IndustrialTargetsManagement/IndustrialValueType.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs delete mode 100644 src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs rename src/Perpetuum/Zones/NpcSystem/{PrimaryLockSelectionStrategy.cs => TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs} (50%) create mode 100644 src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategyBuilder.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategySelector.cs rename src/Perpetuum/Zones/NpcSystem/{PrimaryTargetStrategy.cs => TargettingStrategies/CombatStrategies.cs} (65%) create mode 100644 src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategyBuilder.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategySelector.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs create mode 100644 src/Perpetuum/Zones/RemoteControl/MiningTurret.cs create mode 100644 src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs create mode 100644 src/Perpetuum/Zones/RemoteControl/TurretType.cs diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 836f360c1..15c97b2b0 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -849,7 +849,8 @@ private void RegisterEntities() RegisterRobot(); RegisterRobot(); RegisterRobot(); - + RegisterRobot(); + _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); @@ -950,6 +951,7 @@ private void RegisterEntities() RegisterEntity(); RegisterModule(); + RegisterModule(); RegisterModule(); RegisterModule(); RegisterModule(); @@ -989,7 +991,6 @@ private void RegisterEntities() RegisterEntity(); RegisterUnit(); - //RegisterUnit(); RegisterUnit(); RegisterUnit(); RegisterUnit(); @@ -1115,6 +1116,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_mining_probe_ammo_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_one_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_artifact); @@ -1195,6 +1197,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_energy_transferers); ByCategoryFlags(CategoryFlags.cf_energy_vampires); ByCategoryFlags(CategoryFlags.cf_drillers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_mining_ammo)); + ByCategoryFlags(CategoryFlags.cf_mining_turret_drillers); 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); @@ -1249,6 +1252,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_others); ByCategoryFlags(CategoryFlags.cf_blob_emitter); ByCategoryFlags(CategoryFlags.cf_sentry_turrets); + ByCategoryFlags(CategoryFlags.cf_mining_turrets); ByCategoryFlags(CategoryFlags.cf_reactor_cores); ByCategoryFlags(CategoryFlags.cf_kiosk); ByCategoryFlags(CategoryFlags.cf_alarm_switch); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 94b0e84a7..3dc51f62b 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -109,6 +109,7 @@ public enum CategoryFlags : long cf_wall_builder_ammo = 0x000000000002110A, cf_remote_controlled_units = 0x000000000000200A, cf_sentry_turret_units = 0x000000000010200A, + cf_mining_turret_units = 0x000000000020200A, cf_robot_equipment = 0x000000000000000F, cf_armor_equipment = 0x000000000000010F, @@ -202,6 +203,7 @@ public enum CategoryFlags : long cf_small_drillers = 0x000000000101060F, cf_medium_drillers = 0x000000000201060F, cf_medium_drillers_old = 0x000000000002060F, + cf_mining_turret_drillers = 0x000000000003060F, cf_mining_upgrades = 0x000000000004060F, cf_mining_probes = 0x000000000005060F, cf_harvesters = 0x000000000006060F, @@ -557,6 +559,7 @@ public enum CategoryFlags : long cf_rifts = 0x0000000000000B92, cf_portals = 0x0000000000000C92, cf_sentry_turrets = 0x0000000000000D92, + cf_mining_turrets = 0x0000000000000E92, cf_production_items = 0x0000000000000094, cf_research_kits = 0x0000000000000194, diff --git a/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs b/src/Perpetuum/EntityFramework/EntityDefaultOptions.cs index 623a8c06b..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 { @@ -185,5 +187,15 @@ public int TurretId 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/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 36d498c0e..6334ec250 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -102,7 +102,7 @@ private void LockChangedHandler(Lock @lock) _lock = null; } - private bool IsInRange(Position position) + public bool IsInRange(Position position) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); return !IsRanged || ParentRobot.IsInRangeOf3D(position,OptimalRange + Falloff); diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index fdef4dc24..7fcf1561d 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -22,11 +22,19 @@ 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; @@ -37,31 +45,8 @@ public DrillerModule(CategoryFlags ammoCategoryFlags,RareMaterialHandler rareMat 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; - 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; + base.AcceptVisitor(visitor); } } @@ -73,6 +58,7 @@ public override void UpdateProperty(AggregateField field) case AggregateField.effect_mining_amount_modifier: { _miningAmountModifier.Update(); + return; } } @@ -80,32 +66,74 @@ 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(); + } + + var extractor = new MineralExtractor(location, amount, _materialHelper); + + layer.AcceptVisitor(extractor); + + return new List(extractor.Items); + } + protected override void OnAction() { var zone = Zone; - if ( zone != null ) + + 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(); + var activeGathererModules = 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; + } + + 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; + + if (LIQUIDS.Contains(materialType)) + { + chance /= 2.0; + } + + var rand = FastRandom.NextDouble(); + + if (rand <= chance) + { + return 1; + } + + return 0; } private void DoExtractMinerals(IZone zone) { var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); - var ammo = GetAmmo() as MiningAmmo; + MiningAmmo ammo = GetAmmo() as MiningAmmo; + if (ammo == null) + { return; + } var materialInfo = _materialHelper.GetMaterialInfo(ammo.MaterialType); @@ -114,8 +142,8 @@ private void DoExtractMinerals(IZone zone) var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); var materialAmount = materialInfo.Amount * _miningAmountModifier.Value; var extractedMaterials = Extract(mineralLayer, terrainLock.Location,(uint) materialAmount); - extractedMaterials.Count.ThrowIfEqual(0, ErrorCodes.NoMineralOnTile); + extractedMaterials.Count.ThrowIfEqual(0, ErrorCodes.NoMineralOnTile); extractedMaterials.AddRange(_rareMaterialHandler.GenerateRareMaterials(materialInfo.EntityDefault.Definition)); CreateBeam(terrainLock.Location, BeamState.AlignToTerrain); @@ -123,11 +151,14 @@ private void DoExtractMinerals(IZone zone) using (var scope = Db.CreateTransaction()) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); + var container = ParentRobot.GetContainer(); + Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); var player = ParentRobot as Player; + Debug.Assert(player != null,"player != null"); foreach (var material in extractedMaterials) @@ -136,7 +167,6 @@ private void DoExtractMinerals(IZone zone) item.Owner = Owner; item.Quantity = material.Quantity; - container.AddItem(item, true); var drilledMineralDefinition = material.Definition; @@ -149,7 +179,6 @@ private void DoExtractMinerals(IZone zone) //save container container.Save(); OnGathererMaterial(zone, player, (int) materialInfo.Type); - Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); } @@ -158,40 +187,18 @@ private void DoExtractMinerals(IZone zone) private void CheckEnablerEffect(MaterialInfo materialInfo) { if ( !Zone.Configuration.Terraformable ) + { return; + } - if (!materialInfo.EnablerExtensionRequired) + if (!materialInfo.EnablerExtensionRequired) + { return; + } var containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); - containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); - } - - private const int MAX_EP_PER_DAY = 1440; - - private readonly ISet LIQUIDS = new HashSet(new int[]{ (int)MaterialType.Crude, (int)MaterialType.Liquizit, (int)MaterialType.Epriton }); - protected override int CalculateEp(int materialType) - { - 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; - - if (LIQUIDS.Contains(materialType)) - chance /= 2.0; - - var rand = FastRandom.NextDouble(); - if (rand <= chance) - return 1; - - return 0; + containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs b/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs new file mode 100644 index 000000000..902bcd6f3 --- /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 DrillerModule _module; + + public MiningAmountModifierProperty(DrillerModule 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/RemoteControlledDrillerModule.cs b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs new file mode 100644 index 000000000..349377da9 --- /dev/null +++ b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs @@ -0,0 +1,126 @@ +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Terrains.Materials.Minerals; +using Perpetuum.Zones.Terrains.Materials; +using Perpetuum.Zones; +using System.Collections.Generic; +using System.Diagnostics; +using System.Transactions; +using Perpetuum.Zones.Terrains; +using System.Drawing; + +namespace Perpetuum.Modules +{ + public class RemoteControlledDrillerModule : GathererModule + { + private readonly RareMaterialHandler _rareMaterialHandler; + private readonly MaterialHelper _materialHelper; + + public RemoteControlledDrillerModule(RareMaterialHandler rareMaterialHandler, MaterialHelper materialHelper) + : base(CategoryFlags.undefined, true) + { + _rareMaterialHandler = rareMaterialHandler; + _materialHelper = materialHelper; + } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } + + public List Extract(MineralLayer layer, Point location, uint amount) + { + if (!layer.HasMineral(location)) + { + return new List(); + } + + var extractor = new MineralExtractor(location, amount, _materialHelper); + + layer.AcceptVisitor(extractor); + + return new List(extractor.Items); + } + + protected override void OnAction() + { + var zone = Zone; + + if (zone != null) + { + DoExtractMinerals(zone); + } + } + + protected override int CalculateEp(int materialType) + { + return 0; + } + + private void DoExtractMinerals(IZone zone) + { + var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + var materialType = zone.Terrain.GetMaterialTypeAtPosition(terrainLock.Location); + var materialInfo = _materialHelper.GetMaterialInfo(materialType); + + CheckEnablerEffect(materialInfo); + + var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); + var materialAmount = materialInfo.Amount; + var 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()) + { + Debug.Assert(ParentRobot != null, "ParentRobot != null"); + + var container = ParentRobot.GetContainer(); + + Debug.Assert(container != null, "container != null"); + container.EnlistTransaction(); + + foreach (var material in extractedMaterials) + { + var item = (Item)Factory.CreateWithRandomEID(material.Definition); + + item.Owner = Owner; + item.Quantity = material.Quantity; + container.AddItem(item, true); + } + + //save container + container.Save(); + Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); + scope.Complete(); + } + } + + private void CheckEnablerEffect(MaterialInfo materialInfo) + { + if (!Zone.Configuration.Terraformable) + { + return; + } + + if (!materialInfo.EnablerExtensionRequired) + { + return; + } + + var containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); + + containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); + } + } +} diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 7cbec0792..024c9044b 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -12,6 +12,7 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.RemoteControl; using System; +using System.Diagnostics; using System.Linq; namespace Perpetuum.Modules @@ -69,7 +70,7 @@ public bool HasFreeBandwidthFor(RemoteControlledUnit unit) return bandwidthHandler.HasFreeBandwidthFor(unit); } - public void UseRemoteChannel(SentryTurret turret) + public void UseRemoteChannel(RemoteControlledTurret turret) { bandwidthHandler.UseRemoteChannel(turret); turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; @@ -152,7 +153,16 @@ protected override void OnAction() ammo.CheckEnablerExtensionsAndThrowIfFailed(player.Character, ErrorCodes.ExtensionLevelMismatchTerrain); } - var fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + RemoteControlledTurret fieldTurret = null; + + if (ammo.ED.Options.TurretType == TurretType.Senrty) + { + fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + } + else if (ammo.ED.Options.TurretType == TurretType.Mining) + { + fieldTurret = (MiningTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + } fieldTurret.Owner = this.ParentRobot.Eid; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 42d3f453f..755b7dff9 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -262,9 +262,11 @@ + + @@ -662,6 +664,9 @@ + + + @@ -671,6 +676,8 @@ + + @@ -678,13 +685,25 @@ + + + + + + + + - - - + + + + + + + @@ -695,8 +714,10 @@ + + @@ -780,7 +801,7 @@ - + @@ -1001,6 +1022,7 @@ + diff --git a/src/Perpetuum/Robots/Robot.Locking.cs b/src/Perpetuum/Robots/Robot.Locking.cs index 153a39496..2f308a826 100644 --- a/src/Perpetuum/Robots/Robot.Locking.cs +++ b/src/Perpetuum/Robots/Robot.Locking.cs @@ -35,6 +35,12 @@ 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(); @@ -45,6 +51,11 @@ 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 +81,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/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs index dd969976f..dc8788f37 100644 --- a/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs +++ b/src/Perpetuum/Services/EventServices/EventProcessors/NpcSpawnEventHandlers/NpcReinforcementSpawner.cs @@ -118,7 +118,7 @@ private void SpreadAggro(Presence presenceToAggro, SmartCreature smartCreatureWi { foreach (var threat in smartCreatureWithAggro.ThreatManager.Hostiles) { - npc.AddDirectThreat(threat.unit, threat.Threat + FastRandom.NextDouble(5, 10)); + npc.AddDirectThreat(threat.Unit, threat.Threat + FastRandom.NextDouble(5, 10)); } } } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 608a91b2a..d1659788c 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -932,6 +932,8 @@ protected bool IsHostile(Unit unit) internal virtual bool IsHostile(SentryTurret turret) { return false; } + internal virtual bool IsHostile(MiningTurret turret) { return false; } + internal virtual bool IsHostile(Npc npc) { return false; } public void StopMoving() diff --git a/src/Perpetuum/Zones/Locking/LockHandler.cs b/src/Perpetuum/Zones/Locking/LockHandler.cs index e6c139678..001c975f3 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,76 +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 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; + @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) @@ -222,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); } @@ -230,6 +163,7 @@ public Lock GetLock(long lockId) public void CancelLock(long lockId) { var l = GetLock(lockId); + l?.Cancel(); } @@ -259,9 +193,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) @@ -274,66 +208,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); } + } + + private void OnLockError(Lock @lock, ErrorCodes error) + { + LockError?.Invoke(@lock, error); + } - protected override double CalculateValue() + private void OnLockStateChanged(Lock @lock) + { + if (@lock.State == LockState.Disabled) { - var v = base.CalculateValue(); + RemoveLock(@lock); + } - if (owner.GetCharacter() == Character.None) - return v; + LockStateChanged?.Invoke(@lock); + } - var lockedTargetsMaxBonus = owner.GetPropertyModifier(AggregateField.locked_targets_max_bonus); - v = Math.Min(lockedTargetsMaxBonus.Value + 1, v); + private bool ValidateLock(Lock newLock) + { + var validator = new LockValidator(this); - return v; + newLock.AcceptVisitor(validator); + + 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/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/NpcSystem/AI/AggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs index 8a2b5df9a..508d081e9 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs @@ -68,19 +68,19 @@ private void UpdateHostile(TimeSpan time) return; } - if (!mostHated.unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) + if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) { - this.lastTargetPosition = mostHated.unit.CurrentPosition; + this.lastTargetPosition = mostHated.Unit.CurrentPosition; var findNewTargetPosition = false; - if (!smartCreature.IsInRangeOf3D(mostHated.unit, smartCreature.BestCombatRange)) + if (!smartCreature.IsInRangeOf3D(mostHated.Unit, smartCreature.BestCombatRange)) { findNewTargetPosition = true; } else { - var visibility = smartCreature.GetVisibility(mostHated.unit); + var visibility = smartCreature.GetVisibility(mostHated.Unit); if (visibility != null) { @@ -95,7 +95,7 @@ private void UpdateHostile(TimeSpan time) if (findNewTargetPosition) { - FindNewAttackPositionAsync(mostHated.unit).ContinueWith(t => + FindNewAttackPositionAsync(mostHated.Unit).ContinueWith(t => { if (t.IsCanceled) { @@ -107,7 +107,7 @@ private void UpdateHostile(TimeSpan time) if (path == null) { smartCreature.ThreatManager.Remove(mostHated); - smartCreature.AddPseudoThreat(mostHated.unit); + smartCreature.AddPseudoThreat(mostHated.Unit); return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index 977e9b894..ba26741ed 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -31,7 +31,7 @@ public void Visit(Player player) return; } - if (smartCreature.ThreatManager.Hostiles.Any(h => h.unit.Eid == player.Eid)) + if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == player.Eid)) { return; } @@ -92,7 +92,7 @@ public void Visit(Npc npc) return; } - if (smartCreature.ThreatManager.Hostiles.Any(h => h.unit.Eid == npc.Eid)) + if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == npc.Eid)) { return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index 214af8f81..fec04d0b0 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -2,6 +2,7 @@ using Perpetuum.Timers; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; using Perpetuum.Zones.NpcSystem.ThreatManaging; using Perpetuum.Zones.Terrains; using System; @@ -17,7 +18,7 @@ public class CombatAI : BaseAI private readonly IntervalTimer primarySelectTimer = new IntervalTimer(UpdateFrequency); private List moduleActivators; private TimeSpan hostilesUpdateFrequency = TimeSpan.FromMilliseconds(UpdateFrequency); - private PrimaryLockSelectionStrategySelector stratSelector; + private CombatPrimaryLockSelectionStrategySelector stratSelector; public bool IsNpcHasMissiles { get; set; } = false; @@ -45,11 +46,11 @@ public override void Update(TimeSpan time) RunModules(time); } - protected virtual PrimaryLockSelectionStrategySelector InitSelector() + protected virtual CombatPrimaryLockSelectionStrategySelector InitSelector() { - return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 9) - .WithStrategy(PrimaryLockSelectionStrategy.Random, 1) + return CombatPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(CombatPrimaryLockSelectionStrategy.Hostile, 9) + .WithStrategy(CombatPrimaryLockSelectionStrategy.Random, 1) .Build(); } @@ -110,27 +111,27 @@ protected virtual void SetPrimaryUpdateDelay(bool newPrimary) protected bool IsAttackable(Hostile hostile) { - if (!hostile.unit.InZone) + if (!hostile.Unit.InZone) { return false; } - if (hostile.unit.States.Dead) + if (hostile.Unit.States.Dead) { return false; } - if (!hostile.unit.IsLockable) + if (!hostile.Unit.IsLockable) { return false; } - if (hostile.unit.IsAttackable != ErrorCodes.NoError) + if (hostile.Unit.IsAttackable != ErrorCodes.NoError) { return false; } - if (hostile.unit.IsInvulnerable) + if (hostile.Unit.IsInvulnerable) { return false; } @@ -140,7 +141,7 @@ protected bool IsAttackable(Hostile hostile) return false; } - var isVisible = this.smartCreature.IsVisible(hostile.unit); + var isVisible = this.smartCreature.IsVisible(hostile.Unit); if (!isVisible) { @@ -161,12 +162,12 @@ protected virtual void ProcessHostiles() if (!IsAttackable(hostile)) { this.smartCreature.ThreatManager.Remove(hostile); - this.smartCreature.AddPseudoThreat(hostile.unit); + this.smartCreature.AddPseudoThreat(hostile.Unit); continue; } - if (!this.smartCreature.IsInLockingRange(hostile.unit)) + if (!this.smartCreature.IsInLockingRange(hostile.Unit)) { continue; } @@ -185,7 +186,7 @@ protected bool TryMakeFreeLockSlotFor(Hostile hostile) var weakestLock = this.smartCreature.ThreatManager.Hostiles .SkipWhile(h => h != hostile) .Skip(1) - .Select(h => this.smartCreature.GetLockByUnit(h.unit)) + .Select(h => this.smartCreature.GetLockByUnit(h.Unit)) .LastOrDefault(); if (weakestLock == null) @@ -201,7 +202,7 @@ protected bool TryMakeFreeLockSlotFor(Hostile hostile) protected Hostile GetPrimaryOrMostHatedHostile() { var primaryHostile = this.smartCreature.ThreatManager.Hostiles - .Where(h => h.unit == (this.smartCreature.GetPrimaryLock() as UnitLock)?.Target) + .Where(h => h.Unit == (this.smartCreature.GetPrimaryLock() as UnitLock)?.Target) .FirstOrDefault(); if (primaryHostile != null) @@ -215,13 +216,13 @@ protected Hostile GetPrimaryOrMostHatedHostile() private void SetLockForHostile(Hostile hostile) { var mostHated = GetPrimaryOrMostHatedHostile() == hostile; - var l = this.smartCreature.GetLockByUnit(hostile.unit); + var l = this.smartCreature.GetLockByUnit(hostile.Unit); if (l == null) { if (TryMakeFreeLockSlotFor(hostile)) { - this.smartCreature.AddLock(hostile.unit, mostHated); + this.smartCreature.AddLock(hostile.Unit, mostHated); } } else diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs new file mode 100644 index 000000000..1e92543ba --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -0,0 +1,253 @@ +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; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class IndustrialAI : BaseAI + { + private const int UpdateFrequency = 1650; + private readonly IntervalTimer processIndustrialTargetsTimer = new IntervalTimer(UpdateFrequency); + private readonly IntervalTimer primarySelectTimer = new IntervalTimer(UpdateFrequency); + private List moduleActivators; + private TimeSpan industrialTargetsUpdateFrequency = TimeSpan.FromMilliseconds(UpdateFrequency); + private IndustrialPrimaryLockSelectionStrategySelector stratSelector; + + public IndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Enter() + { + stratSelector = InitSelector(); + moduleActivators = this.smartCreature.ActiveModules + .Select(m => new ModuleActivator(m)) + .ToList(); + processIndustrialTargetsTimer.Update(industrialTargetsUpdateFrequency); + primarySelectTimer.Update(industrialTargetsUpdateFrequency); + + base.Enter(); + } + + public override void Update(TimeSpan time) + { + UpdateIndustrialTargets(time); + UpdatePrimaryTarget(time); + RunModules(time); + } + + protected virtual IndustrialPrimaryLockSelectionStrategySelector InitSelector() + { + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, 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 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() + { + 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; + } + + var weakestLock = this.smartCreature.IndustrialValueManager.IndustrialTargets + .SkipWhile(h => h != industrialTarget) + .Skip(1) + .Select(h => this.smartCreature.GetLockByPosition(h.Position)) + .LastOrDefault(); + + if (weakestLock == null) + { + return false; + } + + weakestLock.Cancel(); + + return true; + } + + protected IndustrialTarget GetPrimaryOrMostValuableIndustrialTarget() + { + 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 = GetPrimaryOrMostValuableIndustrialTarget() == industrialTarget; + var l = this.smartCreature.GetLockByPosition(industrialTarget.Position); + + if (l == null) + { + if (TryMakeFreeLockSlotFor(industrialTarget)) + { + this.smartCreature.AddLock(industrialTarget.Position, mostValuable); + } + } + else + { + if (mostValuable && !l.Primary) + { + this.smartCreature.SetPrimaryLock(l.Id); + } + } + } + + private bool IsLockValidTarget(TerrainLock industrialLock) + { + if (industrialLock == null || industrialLock.State != LockState.Locked) + { + return false; + } + + return industrialLock.Location.IsInRangeOf2D(this.smartCreature.PositionWithHeight, this.smartCreature.MaxIndustrialRange); + } + + 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/IndustrialTurretAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs new file mode 100644 index 000000000..65af18d01 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs @@ -0,0 +1,43 @@ +using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class IndustrialTurretAI : IndustrialAI + { + private readonly IntervalTimer updateFrequency = new IntervalTimer(18000); + + public IndustrialTurretAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() + { + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, 10) + .Build(); + } + + public override void Update(TimeSpan time) + { + FindIndustrialTargets(time); + + base.Update(time); + } + + private void FindIndustrialTargets(TimeSpan time) + { + updateFrequency.Update(time); + + if (updateFrequency.Passed) + { + updateFrequency.Reset(); + smartCreature.LookingForIndustrialTargets(); + } + } + + protected override TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/MiningTurretIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/MiningTurretIndustrialAI.cs new file mode 100644 index 000000000..9fdddab3c --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/MiningTurretIndustrialAI.cs @@ -0,0 +1,16 @@ +using Perpetuum.Zones.NpcSystem.TargettingStrategies; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class MiningTurretIndustrialAI : StationaryIndustrialAI + { + public MiningTurretIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + + protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() + { + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, 1) + .Build(); + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs index 6efcb33de..f07320916 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretCombatAI.cs @@ -1,21 +1,16 @@ -namespace Perpetuum.Zones.NpcSystem.AI +using Perpetuum.Zones.NpcSystem.TargettingStrategies; + +namespace Perpetuum.Zones.NpcSystem.AI { public class SentryTurretCombatAI : StationaryCombatAI { public SentryTurretCombatAI(SmartCreature smartCreature) : base(smartCreature) { } - protected override PrimaryLockSelectionStrategySelector InitSelector() + protected override CombatPrimaryLockSelectionStrategySelector InitSelector() { - return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockSelectionStrategy.HostileOrClosest, 1) + return CombatPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(CombatPrimaryLockSelectionStrategy.HostileOrClosest, 1) .Build(); - - //return PrimaryLockSelectionStrategySelector.Create() - // .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 1) - // .WithStrategy(PrimaryLockSelectionStrategy.Closest, 2) - // .WithStrategy(PrimaryLockSelectionStrategy.OptimalRange, 3) - // .WithStrategy(PrimaryLockSelectionStrategy.Random, 10) - // .Build(); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs index 4e86aa363..812c4e80e 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryCombatAI.cs @@ -1,4 +1,5 @@ using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; using System; namespace Perpetuum.Zones.NpcSystem.AI @@ -9,16 +10,6 @@ public class StationaryCombatAI : CombatAI public StationaryCombatAI(SmartCreature smartCreature) : base(smartCreature) { } - protected override PrimaryLockSelectionStrategySelector InitSelector() - { - return PrimaryLockSelectionStrategySelector.Create() - .WithStrategy(PrimaryLockSelectionStrategy.Hostile, 1) - .WithStrategy(PrimaryLockSelectionStrategy.Closest, 2) - .WithStrategy(PrimaryLockSelectionStrategy.OptimalRange, 3) - .WithStrategy(PrimaryLockSelectionStrategy.Random, 10) - .Build(); - } - public override void Update(TimeSpan time) { FindHostiles(time); @@ -26,6 +17,21 @@ public override void Update(TimeSpan 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); @@ -36,10 +42,5 @@ private void FindHostiles(TimeSpan time) smartCreature.LookingForHostiles(); } } - - protected override TimeSpan SetPrimaryDwellTime() - { - return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); - } } } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs new file mode 100644 index 000000000..d117ab183 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs @@ -0,0 +1,35 @@ +using Perpetuum.Timers; +using System; + +namespace Perpetuum.Zones.NpcSystem.AI +{ + public class StationaryIndustrialAI : IndustrialAI + { + private readonly IntervalTimer updateFrequency = new IntervalTimer(18000); + + public StationaryIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + + public override void Update(TimeSpan time) + { + FindIndustrialTargets(time); + + base.Update(time); + } + + protected override TimeSpan SetPrimaryDwellTime() + { + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + } + + private void FindIndustrialTargets(TimeSpan time) + { + updateFrequency.Update(time); + + if (updateFrequency.Passed) + { + updateFrequency.Reset(); + smartCreature.LookingForIndustrialTargets(); + } + } + } +} diff --git a/src/Perpetuum/Zones/NpcSystem/Creature.cs b/src/Perpetuum/Zones/NpcSystem/Creature.cs index 87ac9c1cd..2f9b1841d 100644 --- a/src/Perpetuum/Zones/NpcSystem/Creature.cs +++ b/src/Perpetuum/Zones/NpcSystem/Creature.cs @@ -96,5 +96,37 @@ 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); + + if (module.ED.AttributeFlags.PrimaryLockedTarget) + { + return primaryLock; + } + + var chance = FastRandom.NextDouble() <= PRIMARY_LOCK_CHANCE_FOR_SECONDARY_MODULE; + + if (primaryLock != null && chance) + { + return primaryLock; + } + + return locks.RandomElement(); + } } } \ No newline at end of file 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 index e401f9134..7ec7deafa 100644 --- a/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs +++ b/src/Perpetuum/Zones/NpcSystem/NPCBossInfo.cs @@ -236,7 +236,7 @@ private void HandleBossOutpostDeath(Npc npc, Unit killer) if (outpost is Outpost) { var participants = npc.ThreatManager.Hostiles - .Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)) + .Select(x => zone.ToPlayerOrGetOwnerPlayer(x.Unit)) .ToList(); var builder = StabilityAffectingEvent.Builder() .WithOutpost(outpost as Outpost) diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index da36cc7b5..e5fd2e702 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -166,6 +166,11 @@ internal override bool IsHostile(SentryTurret turret) return true; } + internal override bool IsHostile(MiningTurret turret) + { + return true; + } + protected override bool IsHostileFor(Unit unit) { return unit.IsHostile(this); @@ -193,7 +198,7 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) if (BossInfo?.IsLootSplit ?? false) { List participants = new List(); - participants = ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.unit)).ToList(); + participants = ThreatManager.Hostiles.Select(x => zone.ToPlayerOrGetOwnerPlayer(x.Unit)).ToList(); if (participants.Count > 0) { @@ -236,9 +241,9 @@ private void HandleNpcDead([NotNull] IZone zone, Unit killer, Player tagger) { var awardedPlayers = new List(); - foreach (var hostile in ThreatManager.Hostiles.Where(x => x.unit is Player)) + foreach (var hostile in ThreatManager.Hostiles.Where(x => x.Unit is Player)) { - var playerUnit = hostile.unit; + var playerUnit = hostile.Unit; var hostilePlayer = zone.ToPlayerOrGetOwnerPlayer(playerUnit); hostilePlayer?.Character.AddExtensionPointsBoostAndLog(EpForActivityType.Npc, EP); diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs deleted file mode 100644 index 7e1716668..000000000 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategyBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Perpetuum.Collections; - -namespace Perpetuum.Zones.NpcSystem -{ - public class PrimaryLockSelectionStrategyBuilder - { - private readonly WeightedCollection selection; - - public PrimaryLockSelectionStrategyBuilder() - { - selection = new WeightedCollection(); - } - - public PrimaryLockSelectionStrategyBuilder WithStrategy(PrimaryLockSelectionStrategy strategy, int weight = 1) - { - selection.Add(strategy, weight); - - return this; - } - - public PrimaryLockSelectionStrategySelector Build() - { - return new PrimaryLockSelectionStrategySelector(selection); - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs b/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs deleted file mode 100644 index dd61335e1..000000000 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategySelector.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Perpetuum.Collections; -using Perpetuum.Zones.Locking.Locks; - -namespace Perpetuum.Zones.NpcSystem -{ - public class PrimaryLockSelectionStrategySelector - { - private readonly WeightedCollection primaryLockSelectionStrategies; - - public PrimaryLockSelectionStrategySelector(WeightedCollection primaryLockSelectionStrategies) - { - this.primaryLockSelectionStrategies = primaryLockSelectionStrategies; - } - - public bool TryUseStrategy(SmartCreature smartCreature, UnitLock[] locks) - { - var primaryLockSelectionStrategy = primaryLockSelectionStrategies.GetRandom(); - - return Strategies.TryInvokeStrategy(primaryLockSelectionStrategy, smartCreature, locks); - } - - public static PrimaryLockSelectionStrategyBuilder Create() - { - return new PrimaryLockSelectionStrategyBuilder(); - } - } -} diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 169579d94..2f624385e 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -9,10 +9,14 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem.AI; 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.Materials.Minerals; using System; using System.Linq; +using Perpetuum.Zones.Terrains.Materials; namespace Perpetuum.Zones.NpcSystem { @@ -21,8 +25,10 @@ public class SmartCreature : Creature private const double AggroRange = 30; private const double BestComnatRangeModifier = 0.9; private const double BaseCallForHelpArmorThreshold = 0.2; + private const int IndustrialScanRange = 30; private readonly ThreatManager threatManager; private readonly PseudoThreatManager pseudoThreatManager; + private readonly IndustrialValueManager industrialValueManager; 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)); @@ -51,6 +57,11 @@ public PseudoThreatManager PseudoThreatManager get { return pseudoThreatManager; } } + public IndustrialValueManager IndustrialValueManager + { + get { return industrialValueManager; } + } + public int BestCombatRange { get { return this.optimalCombatRange.Value; } @@ -66,6 +77,14 @@ public bool IsInHomeRange get { return CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); } } + public int MaxIndustrialRange + { + get + { + return IndustrialScanRange; + } + } + public ISmartCreatureGroup Group { get { return group; } @@ -89,6 +108,7 @@ public SmartCreature() threatManager = new ThreatManager(); AI = new StackFSM(); pseudoThreatManager = new PseudoThreatManager(); + industrialValueManager = new IndustrialValueManager(); } public void LookingForHostiles() @@ -99,6 +119,72 @@ public void LookingForHostiles() } } + /* + Undefined = 0, + Titan,//1 + Crude, //2 + Stermonit, //3 + Imentium, //4 + Liquizit, //5 + Epriton, //6 + Helioptris, //7 + Triandlus, //8 + Prismocitae, //9 + Gravel, //10 + Electrofruit, //11 + Silgium, //12 + Gammaterial, //13 + Plants, //14 + EnergyMineral, //15 + FluxOre //16 + */ + + public void LookingForIndustrialTargets() + { + IndustrialValueManager.Clear(); + + var area = Zone.CreateArea(CurrentPosition, IndustrialScanRange); + + foreach (MaterialType materialType in Enum.GetValues(typeof(MaterialType))) + { + switch (materialType) + { + case MaterialType.Undefined: + case MaterialType.Gravel: + case MaterialType.Plants: + case MaterialType.Electrofruit: + continue; + } + + var builder = MineralScanResultBuilder.Create(Zone, materialType); + + builder.ScanArea = area; + builder.ScanAccuracy = 100.0; + + var result = builder.Build(); + MineralLayer mineralLayer = Zone.Terrain.GetMaterialLayer(materialType) as MineralLayer; + + if (mineralLayer != null) + { + if (result.FoundAny) + { + var valuablePositions = result.Area + .GetPositions() + .Where(x => mineralLayer.HasMineral(x)) + .Select(x=>Zone.FixZ(x)) + .Where(x => x.IsInRangeOf3D(this.PositionWithHeight, this.BestCombatRange)); + + foreach (var valuablePosition in valuablePositions) + { + var mineralNode = mineralLayer.GetNode(valuablePosition); + + IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition, IndustrialValueType.Mineral, mineralNode.GetValue(valuablePosition)); + } + } + } + } + } + public void AddPseudoThreat(Unit hostile) { pseudoThreatManager.AddOrRefreshExisting(hostile); @@ -328,6 +414,10 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { AI.Push(new SentryTurretCombatAI(this)); } + else if (this is MiningTurret) + { + AI.Push(new MiningTurretIndustrialAI(this)); + } else if (IsStationary) { AI.Push(new StationaryIdleAI(this)); @@ -414,7 +504,7 @@ private void HelpingFor(SmartCreature caller) foreach (var hostile in caller.ThreatManager.Hostiles) { - AddThreat(hostile.unit, new Threat(ThreatType.Undefined, hostile.Threat), true); + AddThreat(hostile.Unit, new Threat(ThreatType.Undefined, hostile.Threat), true); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs similarity index 50% rename from src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs rename to src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs index 4e25290d0..6a3403988 100644 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryLockSelectionStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs @@ -1,6 +1,6 @@ -namespace Perpetuum.Zones.NpcSystem +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies { - public enum PrimaryLockSelectionStrategy + public enum CombatPrimaryLockSelectionStrategy { Random, Hostile, 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/PrimaryTargetStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs similarity index 65% rename from src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs rename to src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs index 25f03f47b..61c479828 100644 --- a/src/Perpetuum/Zones/NpcSystem/PrimaryTargetStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs @@ -2,28 +2,28 @@ using System.Collections.Generic; using System.Linq; -namespace Perpetuum.Zones.NpcSystem +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies { - public delegate bool TargetSelectionStrategy(SmartCreature smartCreature, UnitLock[] locks); + public delegate bool CombatTargetSelectionStrategy(SmartCreature smartCreature, UnitLock[] locks); - public static class Strategies + public static class CombatStrategies { - public static Dictionary All = - new Dictionary() + public static Dictionary All = + new Dictionary() { - { PrimaryLockSelectionStrategy.Random, TargetRandom }, - { PrimaryLockSelectionStrategy.Hostile, TargetMostHated }, - { PrimaryLockSelectionStrategy.Closest, TargetClosest }, - { PrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal }, - { PrimaryLockSelectionStrategy.HostileOrClosest, TargetMostHatedOrClosest }, + { CombatPrimaryLockSelectionStrategy.Random, TargetRandom }, + { CombatPrimaryLockSelectionStrategy.Hostile, TargetMostHated }, + { CombatPrimaryLockSelectionStrategy.Closest, TargetClosest }, + { CombatPrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal }, + { CombatPrimaryLockSelectionStrategy.HostileOrClosest, TargetMostHatedOrClosest }, }; - public static TargetSelectionStrategy GetStrategy(PrimaryLockSelectionStrategy strategyType) + public static CombatTargetSelectionStrategy GetStrategy(CombatPrimaryLockSelectionStrategy strategyType) { return All.GetOrDefault(strategyType); } - public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, SmartCreature smartCreature, UnitLock[] locks) + public static bool TryInvokeStrategy(CombatPrimaryLockSelectionStrategy strategyType, SmartCreature smartCreature, UnitLock[] locks) { var primaryLockSelectionStrategy = GetStrategy(strategyType); @@ -38,9 +38,9 @@ public static bool TryInvokeStrategy(PrimaryLockSelectionStrategy strategyType, 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 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) + .OrderByDescending(u => hostiles.Where(h => h.Unit.Eid == u.Target.Eid).FirstOrDefault()?.Threat ?? 0) .FirstOrDefault(); return TrySetPrimaryLock(smartCreature, mostHostileLock); diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs new file mode 100644 index 000000000..d23952cba --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs @@ -0,0 +1,7 @@ +namespace Perpetuum.Zones.NpcSystem.TargettingStrategies +{ + public enum IndustrialPrimaryLockSelectionStrategy + { + RichestTileWithinOptimal, + } +} 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..719b092dd --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs @@ -0,0 +1,58 @@ +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.RichestTileWithinOptimal, TargetRichestTileWithinOptimal }, + }; + + 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 TargetRichestTileWithinOptimal(SmartCreature smartCreature, TerrainLock[] locks) + { + var industrialTargets = smartCreature.IndustrialValueManager.IndustrialTargets; + var industrialTargetLocks = locks.Where(u => industrialTargets.Any(h => h.Position.ToString() == u.Location.ToString())); + var mostHostileLock = industrialTargetLocks + .Where(k => k.Location.IsInRangeOf3D(smartCreature.PositionWithHeight, smartCreature.BestCombatRange)) + .OrderByDescending(u => industrialTargets.Where(h => h.Position.ToString() == u.Location.ToString()).FirstOrDefault()?.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 index fdecf1612..e6c5ab374 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/Hostile.cs @@ -10,7 +10,7 @@ public class Hostile : IComparable private double _threat; - public readonly Unit unit; + public readonly Unit Unit; public TimeSpan LastThreatUpdate { get; private set; } @@ -44,7 +44,7 @@ public bool IsExpired public Hostile(Unit unit) { - this.unit = unit; + this.Unit = unit; Threat = 0.0; } diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs index 9ee3345e2..f7b134fe8 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/ThreatManager.cs @@ -41,7 +41,7 @@ public void Clear() public void Remove(Hostile hostile) { - ImmutableInterlocked.TryRemove(ref _hostiles, hostile.unit.Eid, out hostile); + ImmutableInterlocked.TryRemove(ref _hostiles, hostile.Unit.Eid, out hostile); } public string ToDebugString() @@ -59,7 +59,7 @@ public string ToDebugString() 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.AppendFormat(" {0} ({1}) => {2}", hostile.Unit.ED.Name, hostile.Unit.Eid, hostile.Threat); sb.AppendLine(); } diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index b17cc281c..dc76bfbca 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -31,7 +31,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private const double ENERGY_INJECTOR_THRESHOLD = 0.65; private const double ARMOR_REPAIR_THRESHOLD = 0.95; @@ -358,6 +359,19 @@ public void Visit(CoreBoosterModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } + public void Visit(RemoteControlledDrillerModule module) + { + var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + private void TryActiveModule(LOSResult result, UnitLock primaryLock) { if (result.hit && !result.blockingFlags.HasFlag(BlockingFlags.Plant)) diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index 1f5d2d559..e0a1e64cf 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -47,7 +47,7 @@ public bool HasFreeBandwidthFor(RemoteControlledUnit unit) return BandwidthUsed <= owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; } - public void UseRemoteChannel(SentryTurret turret) + public void UseRemoteChannel(RemoteControlledTurret turret) { UseRemoteChannel(new RemoteChannel(owner, turret)); } @@ -58,7 +58,7 @@ public void UseRemoteChannel(RemoteChannel newChannel) Interlocked.Exchange(ref dirty, 1); } - public void OnRemoteChannelDeactivated(SentryTurret turret) + public void OnRemoteChannelDeactivated(RemoteControlledTurret turret) { var channel = GetRemoteChannelByUnit(turret); diff --git a/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs b/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs new file mode 100644 index 000000000..0d9377f63 --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs @@ -0,0 +1,115 @@ +using Perpetuum.Accounting.Characters; +using Perpetuum.Common.Loggers.Transaction; +using Perpetuum.Containers.SystemContainers; +using Perpetuum.Containers; +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.Items; +using Perpetuum.Log; +using Perpetuum.Modules; +using Perpetuum.Players; +using Perpetuum.Robots; +using Perpetuum.Services.Looting; +using Perpetuum.Services.Sessions; +using Perpetuum.Units; +using Perpetuum.Zones.NpcSystem; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Transactions; +using System; +using Perpetuum.ExportedTypes; +using Perpetuum.Units.DockingBases; + +namespace Perpetuum.Zones.RemoteControl +{ + public class MiningTurret : RemoteControlledTurret + { + public MiningTurret() + { + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + internal override bool IsHostile(Player player) + { + return false; + } + + internal override bool IsHostile(Npc npc) + { + return false; + } + + protected override void OnRemovedFromZone(IZone zone) + { + using (var scope = Db.CreateTransaction()) + { + EnlistTransaction(); + + try + { + var robotInventory = GetContainer(); + + Debug.Assert(robotInventory != null); + + var lootItems = new List(); + + foreach (var item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) + { + var wrapper = item as VolumeWrapperContainer; + + if (wrapper == null) + { + continue; + } + + lootItems.AddRange(wrapper.GetLootItems()); + wrapper.SetAllowDelete(); + Repository.Delete(wrapper); + } + + foreach (var 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()); + } + else + { + robotInventory.RemoveItemOrThrow(item); + Repository.Delete(item); + } + } + + if (lootItems.Count > 0) + { + var lootContainer = LootContainer.Create() + .AddLoot(lootItems) + .BuildAndAddToZone(zone, CurrentPosition); + } + + this.Save(); + + scope.Complete(); + } + catch (Exception ex) + { + Logger.Exception(ex); + } + } + + base.OnRemovedFromZone(zone); + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs index 2b2a1629c..5281868ab 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs @@ -1,39 +1,38 @@ -using Perpetuum.Robots; -using Perpetuum.Units; -using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.Locking; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Perpetuum.Timers; +using Perpetuum.Zones.Locking.Locks; using Perpetuum.Modules; namespace Perpetuum.Zones.RemoteControl { - public delegate void RemoteChannelEventHandler(SentryTurret turret); + public delegate void RemoteChannelEventHandler(RemoteControlledTurret turret); public delegate void RemoteChannelEventHandler(RemoteChannel channel, T arg); public class RemoteChannel { public long Id { get; private set; } - public RemoteChannel(RemoteControllerModule owner, SentryTurret turret) + public RemoteControllerModule Owner { get; private set; } + + public RemoteControlledTurret Turret { get; set; } + + public RemoteChannel(RemoteControllerModule owner, RemoteControlledTurret turret) { Id = FastRandom.NextLong(); Owner = owner; Turret = turret; } - public RemoteControllerModule Owner { get; private set; } - - public SentryTurret Turret { get; set; } - public virtual bool Equals(Lock other) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + return Id == other.Id; } } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs new file mode 100644 index 000000000..92fcd7e40 --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs @@ -0,0 +1,65 @@ +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Units; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.AI; +using System; + +namespace Perpetuum.Zones.RemoteControl +{ + public class RemoteControlledTurret : SmartCreature + { + private const double SentryTurretCallForHelpArmorThreshold = 0.8; + private UnitDespawnHelper despawnHelper; + private ItemProperty rcBandwidthUsage = ItemProperty.None; + + public event RemoteChannelEventHandler RemoteChannelDeactivated; + + public double RemoteChannelBandwidthUsage + { + get { return rcBandwidthUsage.Value; } + } + + public TimeSpan DespawnTime + { + set { despawnHelper = UnitDespawnHelper.Create(this, value); } + } + + public override void Initialize() + { + rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); + AddProperty(rcBandwidthUsage); + Behavior = Behavior.Create(BehaviorType.RemoteControlled); + + base.Initialize(); + } + + public override bool IsStationary => true; + + public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; + + protected override void OnUpdate(TimeSpan time) + { + base.OnUpdate(time); + despawnHelper?.Update(time, this); + } + + protected override void OnRemovedFromZone(IZone zone) + { + RemoteChannelDeactivated(this); + base.OnRemovedFromZone(zone); + } + + protected override void OnDead(Unit killer) + { + Zone.CreateBeam( + BeamType.arbalest_wreck, + builder => builder + .WithPosition(CurrentPosition) + .WithState(BeamState.Hit)); + + base.OnDead(killer); + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 12023f9d3..89ff23ff0 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -4,58 +4,20 @@ using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; -using Perpetuum.Zones.Beams; using Perpetuum.Zones.NpcSystem; -using Perpetuum.Zones.NpcSystem.AI; -using System; namespace Perpetuum.Zones.RemoteControl { - public class SentryTurret : SmartCreature + public class SentryTurret : RemoteControlledTurret { - private UnitDespawnHelper despawnHelper; - private const double SentryTurretCallForHelpArmorThreshold = 0.8; private readonly IStandingHandler standingHandler; private const double StandingLimit = 0.0; - public event RemoteChannelEventHandler RemoteChannelDeactivated; - - private ItemProperty rcBandwidthUsage = ItemProperty.None; - - public double RemoteChannelBandwidthUsage - { - get { return rcBandwidthUsage.Value; } - } - - public TimeSpan DespawnTime - { - set { despawnHelper = UnitDespawnHelper.Create(this, value); } - } - public SentryTurret(IStandingHandler standingHandler) { this.standingHandler = standingHandler; } - public override void Initialize() - { - rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); - AddProperty(rcBandwidthUsage); - Behavior = Behavior.Create(BehaviorType.RemoteControlled); - - base.Initialize(); - } - - public override bool IsStationary => true; - - public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; - - protected override void OnUpdate(TimeSpan time) - { - base.OnUpdate(time); - despawnHelper?.Update(time, this); - } - protected override bool IsHostileFor(Unit unit) { return unit.IsHostile(this); @@ -84,23 +46,6 @@ protected override void UpdateUnitVisibility(Unit target) } } - protected override void OnRemovedFromZone(IZone zone) - { - RemoteChannelDeactivated(this); - base.OnRemovedFromZone(zone); - } - - protected override void OnDead(Unit killer) - { - Zone.CreateBeam( - BeamType.arbalest_wreck, - builder => builder - .WithPosition(CurrentPosition) - .WithState(BeamState.Hit)); - - base.OnDead(killer); - } - private bool IsHostileCorporation(long corporationEid) { if (DefaultCorporationDataCache.IsCorporationDefault(corporationEid)) diff --git a/src/Perpetuum/Zones/RemoteControl/TurretType.cs b/src/Perpetuum/Zones/RemoteControl/TurretType.cs new file mode 100644 index 000000000..b271c28ba --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/TurretType.cs @@ -0,0 +1,8 @@ +namespace Perpetuum.Zones.RemoteControl +{ + public enum TurretType + { + Senrty, + Mining, + } +} 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/Terrains/TerrainExtensions.cs b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs index 6e8248b47..f2a900652 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -299,6 +300,20 @@ 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; + } + private const int GZIP_THRESHOLD = 260; [CanBeNull] From 8051711e605e75d9b1055e54fe2b29e64a8102c9 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Mon, 4 Dec 2023 21:32:19 +0500 Subject: [PATCH 421/457] - Mining turrets fixes and improvements. Fully functional now. --- .../PerpetuumBootstrapper.cs | 4 +- .../MiningAmountModifierProperty.cs | 4 +- .../Modules/RemoteControlledDrillerModule.cs | 6 ++- .../Modules/RemoteControllerModule.cs | 11 +++--- .../Zones/NpcSystem/AI/IndustrialTurretAI.cs | 2 +- .../Zones/NpcSystem/SmartCreature.cs | 37 +++---------------- .../Zones/RemoteControl/MiningTurret.cs | 13 +------ .../Zones/RemoteControl/TurretType.cs | 2 +- .../Zones/Terrains/TerrainExtensions.cs | 9 +++++ 9 files changed, 32 insertions(+), 56 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 15c97b2b0..bcac3c6b4 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -846,10 +846,10 @@ 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(); RegisterRobot(); - RegisterRobot(); - RegisterRobot(); _builder.RegisterType().As().PropertiesAutowired().SingleInstance(); diff --git a/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs b/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs index 902bcd6f3..b96261939 100644 --- a/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs +++ b/src/Perpetuum/Modules/ModuleProperties/MiningAmountModifierProperty.cs @@ -4,9 +4,9 @@ namespace Perpetuum.Modules.ModuleProperties { public class MiningAmountModifierProperty : ModuleProperty { - private readonly DrillerModule _module; + private readonly GathererModule _module; - public MiningAmountModifierProperty(DrillerModule module) : base(module, AggregateField.mining_amount_modifier) + public MiningAmountModifierProperty(GathererModule module) : base(module, AggregateField.mining_amount_modifier) { _module = module; AddEffectModifier(AggregateField.effect_mining_amount_modifier); diff --git a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs index 349377da9..7b417a87d 100644 --- a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs @@ -12,6 +12,7 @@ using System.Transactions; using Perpetuum.Zones.Terrains; using System.Drawing; +using Perpetuum.Modules.ModuleProperties; namespace Perpetuum.Modules { @@ -19,12 +20,15 @@ public class RemoteControlledDrillerModule : GathererModule { private readonly RareMaterialHandler _rareMaterialHandler; private readonly MaterialHelper _materialHelper; + private readonly ItemProperty _miningAmountModifier; public RemoteControlledDrillerModule(RareMaterialHandler rareMaterialHandler, MaterialHelper materialHelper) : base(CategoryFlags.undefined, true) { _rareMaterialHandler = rareMaterialHandler; _materialHelper = materialHelper; + _miningAmountModifier = new MiningAmountModifierProperty(this); + AddProperty(_miningAmountModifier); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -73,7 +77,7 @@ private void DoExtractMinerals(IZone zone) CheckEnablerEffect(materialInfo); var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); - var materialAmount = materialInfo.Amount; + var materialAmount = materialInfo.Amount * _miningAmountModifier.Value; var extractedMaterials = Extract(mineralLayer, terrainLock.Location, (uint)materialAmount); extractedMaterials.Count.ThrowIfEqual(0, ErrorCodes.NoMineralOnTile); diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 024c9044b..781203405 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -12,7 +12,6 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.RemoteControl; using System; -using System.Diagnostics; using System.Linq; namespace Perpetuum.Modules @@ -117,11 +116,11 @@ protected override void OnAction() if (myLock is TerrainLock) { - lockPosition = (myLock as TerrainLock).Location.AddToZ(SentryTurretHeight); + lockPosition = (myLock as TerrainLock).Location; } else if (myLock is UnitLock) { - lockPosition = (myLock as UnitLock).Target.CurrentPosition.AddToZ(SentryTurretHeight); + lockPosition = (myLock as UnitLock).Target.CurrentPosition; } else { @@ -133,11 +132,11 @@ protected override void OnAction() Position targetPosition = lockPosition.Value; zone.Units .OfType() - .WithinRange(targetPosition, SentryTurretDeployRange) + .WithinRange(zone.FixZ(targetPosition), SentryTurretDeployRange) .Any() .ThrowIfTrue(ErrorCodes.BlobEmitterInRange); - var r = zone.IsInLineOfSight(ParentRobot, targetPosition, false); + var r = zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); if (r.hit) { @@ -155,7 +154,7 @@ protected override void OnAction() RemoteControlledTurret fieldTurret = null; - if (ammo.ED.Options.TurretType == TurretType.Senrty) + if (ammo.ED.Options.TurretType == TurretType.Sentry) { fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs index 65af18d01..0b251f583 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs @@ -6,7 +6,7 @@ namespace Perpetuum.Zones.NpcSystem.AI { public class IndustrialTurretAI : IndustrialAI { - private readonly IntervalTimer updateFrequency = new IntervalTimer(18000); + private readonly IntervalTimer updateFrequency = new IntervalTimer(2000); public IndustrialTurretAI(SmartCreature smartCreature) : base(smartCreature) { } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 2f624385e..a2fceca1a 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -17,6 +17,7 @@ using System; using System.Linq; using Perpetuum.Zones.Terrains.Materials; +using Perpetuum.Zones.Terrains; namespace Perpetuum.Zones.NpcSystem { @@ -119,49 +120,23 @@ public void LookingForHostiles() } } - /* - Undefined = 0, - Titan,//1 - Crude, //2 - Stermonit, //3 - Imentium, //4 - Liquizit, //5 - Epriton, //6 - Helioptris, //7 - Triandlus, //8 - Prismocitae, //9 - Gravel, //10 - Electrofruit, //11 - Silgium, //12 - Gammaterial, //13 - Plants, //14 - EnergyMineral, //15 - FluxOre //16 - */ - public void LookingForIndustrialTargets() { IndustrialValueManager.Clear(); var area = Zone.CreateArea(CurrentPosition, IndustrialScanRange); - foreach (MaterialType materialType in Enum.GetValues(typeof(MaterialType))) - { - switch (materialType) - { - case MaterialType.Undefined: - case MaterialType.Gravel: - case MaterialType.Plants: - case MaterialType.Electrofruit: - continue; - } + var availableMaterialTypes = Zone.Terrain.GetAvailableMaterialTypes(); + foreach (MaterialType materialType in availableMaterialTypes) + { var builder = MineralScanResultBuilder.Create(Zone, materialType); builder.ScanArea = area; builder.ScanAccuracy = 100.0; var result = builder.Build(); + MineralLayer mineralLayer = Zone.Terrain.GetMaterialLayer(materialType) as MineralLayer; if (mineralLayer != null) @@ -178,7 +153,7 @@ public void LookingForIndustrialTargets() { var mineralNode = mineralLayer.GetNode(valuablePosition); - IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition, IndustrialValueType.Mineral, mineralNode.GetValue(valuablePosition)); + IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Mineral, mineralNode.GetValue(valuablePosition)); } } } diff --git a/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs b/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs index 0d9377f63..a5cfe36ac 100644 --- a/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs @@ -1,25 +1,14 @@ -using Perpetuum.Accounting.Characters; -using Perpetuum.Common.Loggers.Transaction; -using Perpetuum.Containers.SystemContainers; -using Perpetuum.Containers; +using Perpetuum.Containers; using Perpetuum.Data; -using Perpetuum.EntityFramework; -using Perpetuum.Items; using Perpetuum.Log; -using Perpetuum.Modules; using Perpetuum.Players; -using Perpetuum.Robots; using Perpetuum.Services.Looting; -using Perpetuum.Services.Sessions; using Perpetuum.Units; using Perpetuum.Zones.NpcSystem; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Transactions; using System; -using Perpetuum.ExportedTypes; -using Perpetuum.Units.DockingBases; namespace Perpetuum.Zones.RemoteControl { diff --git a/src/Perpetuum/Zones/RemoteControl/TurretType.cs b/src/Perpetuum/Zones/RemoteControl/TurretType.cs index b271c28ba..a978eef6c 100644 --- a/src/Perpetuum/Zones/RemoteControl/TurretType.cs +++ b/src/Perpetuum/Zones/RemoteControl/TurretType.cs @@ -2,7 +2,7 @@ { public enum TurretType { - Senrty, + Sentry, Mining, } } diff --git a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs index f2a900652..2524a4bf2 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs @@ -314,6 +314,15 @@ public static MaterialType GetMaterialTypeAtPosition(this ITerrain terrain, Posi : (layer as MineralLayer).Type; } + public static MaterialType[] GetAvailableMaterialTypes(this ITerrain terrain) + { + MaterialType[] materials = Enum.GetValues(typeof(MaterialType)) as MaterialType[]; + + return materials + .Where(x => terrain.GetMaterialLayer(x) != null) + .ToArray(); + } + private const int GZIP_THRESHOLD = 260; [CanBeNull] From fd8b0a09b693739bf4900315fb498633ac8c49f8 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Mon, 4 Dec 2023 22:06:40 +0500 Subject: [PATCH 422/457] - Little more order in files --- src/Perpetuum/Perpetuum.csproj | 17 +- src/Perpetuum/Units/Unit.cs | 8 +- src/Perpetuum/Zones/NpcSystem/AI/ActiveAI.cs | 138 ++++ src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs | 1 + .../AI/{ => Behaviors}/AggressiveBehavior.cs | 2 +- .../NpcSystem/AI/{ => Behaviors}/Behavior.cs | 2 +- .../AI/{ => Behaviors}/BehaviorType.cs | 2 +- .../AI/{ => Behaviors}/NeutralBehavior.cs | 2 +- .../AI/{ => Behaviors}/PassiveBehavior.cs | 2 +- .../RemoteControlledBehavior.cs | 2 +- .../NpcSystem/AI/BodyPullThreatHelper.cs | 1 + src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 1 + .../Zones/NpcSystem/AI/InactiveAI.cs | 24 + .../Zones/NpcSystem/AI/IndustrialAI.cs | 1 + .../Zones/NpcSystem/AI/ModuleActivator.cs | 379 +++++++++++ src/Perpetuum/Zones/NpcSystem/AI/NullAI.cs | 9 + .../Zones/NpcSystem/AI/SentryTurretIdleAI.cs | 4 +- .../Zones/NpcSystem/AI/StationaryIdleAI.cs | 3 +- src/Perpetuum/Zones/NpcSystem/AI/TurretAI.cs | 51 ++ src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs | 2 +- .../NpcSystem/Flocks/FlockConfiguration.cs | 2 +- .../Flocks/FlockConfigurationRepository.cs | 2 +- .../NpcSystem/Flocks/IFlockConfiguration.cs | 2 +- src/Perpetuum/Zones/NpcSystem/Npc.cs | 2 +- .../Zones/NpcSystem/SmartCreature.cs | 1 + src/Perpetuum/Zones/NpcSystem/Turret.cs | 597 +----------------- src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs | 2 +- .../Zones/RemoteControl/MiningTurret.cs | 2 +- .../RemoteControl/RemoteControlledTurret.cs | 2 +- .../Zones/RemoteControl/SentryTurret.cs | 2 +- 30 files changed, 662 insertions(+), 603 deletions(-) create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/ActiveAI.cs rename src/Perpetuum/Zones/NpcSystem/AI/{ => Behaviors}/AggressiveBehavior.cs (74%) rename src/Perpetuum/Zones/NpcSystem/AI/{ => Behaviors}/Behavior.cs (93%) rename src/Perpetuum/Zones/NpcSystem/AI/{ => Behaviors}/BehaviorType.cs (70%) rename src/Perpetuum/Zones/NpcSystem/AI/{ => Behaviors}/NeutralBehavior.cs (73%) rename src/Perpetuum/Zones/NpcSystem/AI/{ => Behaviors}/PassiveBehavior.cs (73%) rename src/Perpetuum/Zones/NpcSystem/AI/{ => Behaviors}/RemoteControlledBehavior.cs (76%) create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/InactiveAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/NullAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/TurretAI.cs diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 755b7dff9..637379a33 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -667,26 +667,31 @@ - + + - - + + + - - - + + + + + + diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index d1659788c..a72c96fbc 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -917,14 +917,14 @@ public Packet Build() } } - 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; } + public virtual bool IsHostile(Player player) { return false; } internal virtual bool IsHostile(AreaBomb bomb) { return false; } 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/BaseAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs index 8742ce8f4..5933d856c 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs @@ -1,5 +1,6 @@ using Perpetuum.Log; using Perpetuum.StateMachines; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; using System.Diagnostics; diff --git a/src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/AggressiveBehavior.cs similarity index 74% rename from src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs rename to src/Perpetuum/Zones/NpcSystem/AI/Behaviors/AggressiveBehavior.cs index 0ea174c16..4d701aabf 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/AggressiveBehavior.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/AggressiveBehavior.cs @@ -1,4 +1,4 @@ -namespace Perpetuum.Zones.NpcSystem.AI +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors { public class AggressiveBehavior : Behavior { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs similarity index 93% rename from src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs rename to src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs index c136503d2..7ae89c890 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/Behavior.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs @@ -1,4 +1,4 @@ -namespace Perpetuum.Zones.NpcSystem.AI +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors { public class Behavior { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BehaviorType.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs similarity index 70% rename from src/Perpetuum/Zones/NpcSystem/AI/BehaviorType.cs rename to src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs index d11f7ef19..f1fdd4639 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BehaviorType.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs @@ -1,4 +1,4 @@ -namespace Perpetuum.Zones.NpcSystem.AI +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors { public enum BehaviorType { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/NeutralBehavior.cs similarity index 73% rename from src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs rename to src/Perpetuum/Zones/NpcSystem/AI/Behaviors/NeutralBehavior.cs index 5c9f47368..64a412026 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/NeutralBehavior.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/NeutralBehavior.cs @@ -1,4 +1,4 @@ -namespace Perpetuum.Zones.NpcSystem.AI +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors { public class NeutralBehavior : Behavior { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/PassiveBehavior.cs similarity index 73% rename from src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs rename to src/Perpetuum/Zones/NpcSystem/AI/Behaviors/PassiveBehavior.cs index ab2b705f6..43e695fb2 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/PassiveBehavior.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/PassiveBehavior.cs @@ -1,4 +1,4 @@ -namespace Perpetuum.Zones.NpcSystem.AI +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors { public class PassiveBehavior : Behavior { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs similarity index 76% rename from src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs rename to src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs index 1004a5840..f838f6de0 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/RemoteControlledBehavior.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs @@ -1,4 +1,4 @@ -namespace Perpetuum.Zones.NpcSystem.AI +namespace Perpetuum.Zones.NpcSystem.AI.Behaviors { public class RemoteControlledBehavior : Behavior { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index ba26741ed..d63707021 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -3,6 +3,7 @@ using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones.Eggs; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.NpcSystem.ThreatManaging; using System.Linq; diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index fec04d0b0..b1136850b 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -2,6 +2,7 @@ using Perpetuum.Timers; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.NpcSystem.TargettingStrategies; using Perpetuum.Zones.NpcSystem.ThreatManaging; using Perpetuum.Zones.Terrains; 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 index 1e92543ba..e10b51307 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using Perpetuum.Zones.NpcSystem.IndustrialTargetsManagement; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; namespace Perpetuum.Zones.NpcSystem.AI { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs new file mode 100644 index 000000000..c354c7a5e --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs @@ -0,0 +1,379 @@ +using Perpetuum.EntityFramework; +using Perpetuum.Modules.EffectModules; +using Perpetuum.Modules.Weapons; +using Perpetuum.Modules; +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 + { + 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) + { + 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); + } + + 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; + } + + var 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; + } + + var 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; + } + + var 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; + } + + var 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; + } + + var 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; + } + + var 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; + } + + 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 && 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; + } + + 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 && 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) + { + var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + 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/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/SentryTurretIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs index 5e194f35a..8ba044afa 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/SentryTurretIdleAI.cs @@ -1,4 +1,6 @@ -namespace Perpetuum.Zones.NpcSystem.AI +using Perpetuum.Zones.NpcSystem.AI.Behaviors; + +namespace Perpetuum.Zones.NpcSystem.AI { public class SentryTurretIdleAI : StationaryIdleAI { diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs index cffd971b2..5ddec3337 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIdleAI.cs @@ -1,4 +1,5 @@ -using System; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using System; namespace Perpetuum.Zones.NpcSystem.AI { 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/Flocks/Flock.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs index fa89c7f18..1511955b8 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/Flock.cs @@ -3,7 +3,7 @@ using Perpetuum.Services.Looting; using Perpetuum.Units; using Perpetuum.Zones.Finders.PositionFinders; -using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.NpcSystem.Presences; using Perpetuum.Zones.NpcSystem.Presences.InterzonePresences; using System; diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs index 34b6c2217..1f1245d31 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfiguration.cs @@ -1,5 +1,5 @@ using Perpetuum.EntityFramework; -using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; namespace Perpetuum.Zones.NpcSystem.Flocks diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs index d40143f4e..f2946f4e8 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/FlockConfigurationRepository.cs @@ -1,6 +1,6 @@ using Perpetuum.Data; using Perpetuum.EntityFramework; -using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs index ac94a966e..643a9fb79 100644 --- a/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs +++ b/src/Perpetuum/Zones/NpcSystem/Flocks/IFlockConfiguration.cs @@ -1,5 +1,5 @@ using Perpetuum.EntityFramework; -using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; namespace Perpetuum.Zones.NpcSystem.Flocks diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index e5fd2e702..f01d63f2b 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -151,7 +151,7 @@ public override string InfoString } } - internal override bool IsHostile(Player player) + public override bool IsHostile(Player player) { return true; } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index a2fceca1a..f6726074b 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -18,6 +18,7 @@ using System.Linq; using Perpetuum.Zones.Terrains.Materials; using Perpetuum.Zones.Terrains; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; namespace Perpetuum.Zones.NpcSystem { diff --git a/src/Perpetuum/Zones/NpcSystem/Turret.cs b/src/Perpetuum/Zones/NpcSystem/Turret.cs index dc76bfbca..c04c9e025 100644 --- a/src/Perpetuum/Zones/NpcSystem/Turret.cs +++ b/src/Perpetuum/Zones/NpcSystem/Turret.cs @@ -1,403 +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, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor, - IEntityVisitor + public class Turret : Creature { - 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) - { - 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); - } - - 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; - } - - var 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; - } - - var 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; - } - - var 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; - } - - var 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; - } - - var 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; - } - - var 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; - } - - 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 && 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; - } - - 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 && 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); - } + private readonly NullAI _nullAI; + private readonly FiniteStateMachine _aiStateMachine; - public void Visit(RemoteControlledDrillerModule module) + protected TurretAI AI { - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); - - if (lockTarget == null) - { - return; - } - - module.Lock = lockTarget; - module.State.SwitchTo(ModuleStateType.Oneshot); + get { return _aiStateMachine.Current ?? _nullAI; } } - private void TryActiveModule(LOSResult result, UnitLock primaryLock) + public FiniteStateMachine AIStateMachine { - if (result.hit && !result.blockingFlags.HasFlag(BlockingFlags.Plant)) - return; - - _module.Lock = primaryLock; - _module.State.SwitchTo(ModuleStateType.Oneshot); + 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) @@ -421,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/Turrets/PBSTurret.cs b/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs index 4af9f87f1..72207dee7 100644 --- a/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs +++ b/src/Perpetuum/Zones/PBS/Turrets/PBSTurret.cs @@ -196,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/RemoteControl/MiningTurret.cs b/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs index a5cfe36ac..40598bc32 100644 --- a/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs @@ -23,7 +23,7 @@ protected override bool IsHostileFor(Unit unit) return unit.IsHostile(this); } - internal override bool IsHostile(Player player) + public override bool IsHostile(Player player) { return false; } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs index 92fcd7e40..1621f2e5b 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs @@ -3,7 +3,7 @@ using Perpetuum.Units; using Perpetuum.Zones.Beams; using Perpetuum.Zones.NpcSystem; -using Perpetuum.Zones.NpcSystem.AI; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; namespace Perpetuum.Zones.RemoteControl diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 89ff23ff0..9c183aa38 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -23,7 +23,7 @@ protected override bool IsHostileFor(Unit unit) return unit.IsHostile(this); } - internal override bool IsHostile(Player player) + public override bool IsHostile(Player player) { if (Owner == player.Eid) { From 4e89d804e82e855755099d4bd1480a83816446c8 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Tue, 5 Dec 2023 11:16:25 +0500 Subject: [PATCH 423/457] - Harvesting turrets, fully functional --- .../PerpetuumBootstrapper.cs | 10 +- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 5 +- src/Perpetuum/Modules/GathererModule.cs | 34 ++++++ src/Perpetuum/Modules/HarvesterModule.cs | 110 ++++------------- .../HarvestingAmountModifierProperty.cs | 71 +++++++++++ .../Modules/RemoteControlledDrillerModule.cs | 16 +++ .../RemoteControlledHarvesterModule.cs | 111 ++++++++++++++++++ .../Modules/RemoteControllerModule.cs | 9 +- src/Perpetuum/Perpetuum.csproj | 10 +- src/Perpetuum/Units/Unit.cs | 2 +- .../AI/HarvestingIndustrialTurretAI.cs | 27 +++++ ...lAI.cs => HarvestingTurretIndustrialAI.cs} | 6 +- .../Zones/NpcSystem/AI/IndustrialAI.cs | 9 +- .../Zones/NpcSystem/AI/IndustrialTurretAI.cs | 43 ------- .../NpcSystem/AI/MiningIndustrialTurretAI.cs | 27 +++++ .../Zones/NpcSystem/AI/ModuleActivator.cs | 16 ++- .../NpcSystem/AI/StationaryIndustrialAI.cs | 22 +--- src/Perpetuum/Zones/NpcSystem/Creature.cs | 14 +-- src/Perpetuum/Zones/NpcSystem/Npc.cs | 2 +- .../Zones/NpcSystem/SmartCreature.cs | 47 +++++++- .../IndustrialPrimaryLockSelectionStrategy.cs | 3 +- .../IndustrialStrategies.cs | 14 ++- .../{MiningTurret.cs => IndustrialTurret.cs} | 11 +- .../Zones/RemoteControl/TurretType.cs | 1 + .../Plants/Harvesters/PlantHarvester.cs | 8 +- .../Zones/Terrains/TerrainExtensions.cs | 28 ++++- 26 files changed, 459 insertions(+), 197 deletions(-) create mode 100644 src/Perpetuum/Modules/GathererModule.cs create mode 100644 src/Perpetuum/Modules/ModuleProperties/HarvestingAmountModifierProperty.cs create mode 100644 src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/HarvestingIndustrialTurretAI.cs rename src/Perpetuum/Zones/NpcSystem/AI/{MiningTurretIndustrialAI.cs => HarvestingTurretIndustrialAI.cs} (64%) delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/MiningIndustrialTurretAI.cs rename src/Perpetuum/Zones/RemoteControl/{MiningTurret.cs => IndustrialTurret.cs} (91%) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index bcac3c6b4..3f7b18407 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -847,7 +847,7 @@ 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(); @@ -952,6 +952,7 @@ private void RegisterEntities() RegisterModule(); RegisterModule(); + RegisterModule(); RegisterModule(); RegisterModule(); RegisterModule(); @@ -1117,6 +1118,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_mining_probe_ammo_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_one_tile); ByCategoryFlags(CategoryFlags.cf_mining_probe_ammo_artifact); @@ -1197,7 +1199,8 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_energy_transferers); ByCategoryFlags(CategoryFlags.cf_energy_vampires); ByCategoryFlags(CategoryFlags.cf_drillers,new NamedParameter("ammoCategoryFlags",CategoryFlags.cf_mining_ammo)); - ByCategoryFlags(CategoryFlags.cf_mining_turret_drillers); + 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); @@ -1252,7 +1255,8 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_others); ByCategoryFlags(CategoryFlags.cf_blob_emitter); ByCategoryFlags(CategoryFlags.cf_sentry_turrets); - ByCategoryFlags(CategoryFlags.cf_mining_turrets); + ByCategoryFlags(CategoryFlags.cf_mining_turrets); + ByCategoryFlags(CategoryFlags.cf_harvesting_turrets); ByCategoryFlags(CategoryFlags.cf_reactor_cores); ByCategoryFlags(CategoryFlags.cf_kiosk); ByCategoryFlags(CategoryFlags.cf_alarm_switch); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 3dc51f62b..f2554d13a 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -110,6 +110,7 @@ public enum CategoryFlags : long cf_remote_controlled_units = 0x000000000000200A, cf_sentry_turret_units = 0x000000000010200A, cf_mining_turret_units = 0x000000000020200A, + cf_harvesting_turret_units = 0x000000000030200A, cf_robot_equipment = 0x000000000000000F, cf_armor_equipment = 0x000000000000010F, @@ -203,13 +204,14 @@ public enum CategoryFlags : long cf_small_drillers = 0x000000000101060F, cf_medium_drillers = 0x000000000201060F, cf_medium_drillers_old = 0x000000000002060F, - cf_mining_turret_drillers = 0x000000000003060F, + 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, @@ -560,6 +562,7 @@ public enum CategoryFlags : long cf_portals = 0x0000000000000C92, cf_sentry_turrets = 0x0000000000000D92, cf_mining_turrets = 0x0000000000000E92, + cf_harvesting_turrets = 0x0000000000000F92, cf_production_items = 0x0000000000000094, cf_research_kits = 0x0000000000000194, 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 0988dcde4..9cd20db62 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -13,36 +13,13 @@ using Perpetuum.Zones.Beams; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Terrains; -using Perpetuum.Zones.Terrains.Materials.Plants; using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; 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); - } - } - public class HarvesterModule : GathererModule { + private const int MAX_EP_PER_DAY = 1440; private readonly PlantHarvester.Factory _plantHarvesterFactory; private readonly HarvestingAmountModifierProperty _harverstingAmountModifier; @@ -51,14 +28,15 @@ public HarvesterModule(CategoryFlags ammoCategoryFlags,PlantHarvester.Factory pl _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) @@ -71,29 +49,33 @@ public override void UpdateProperty(AggregateField field) case AggregateField.effect_harvesting_amount_modifier: { _harverstingAmountModifier.Update(); + break; } } } - private const int MAX_EP_PER_DAY = 1440; - protected override int CalculateEp(int materialType) { 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; } @@ -101,8 +83,11 @@ protected override int CalculateEp(int materialType) protected override void OnAction() { var zone = Zone; + if (zone == null) + { return; + } DoHarvesting(zone); ConsumeAmmo(); @@ -111,6 +96,7 @@ protected override void OnAction() private void DoHarvesting(IZone zone) { var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + CreateBeam(terrainLock.Location, BeamState.AlignToTerrain); using (var scope = Db.CreateTransaction()) @@ -119,23 +105,28 @@ private void DoHarvesting(IZone zone) { var plantInfo = zone.Terrain.Plants.GetValue(terrainLock.Location); var amountModifier = _harverstingAmountModifier.GetValueByPlantType(plantInfo.type); + IPlantHarvester plantHarvester = _plantHarvesterFactory(zone, amountModifier); + var harvestedPlants = plantHarvester.HarvestPlant(terrainLock.Location); Debug.Assert(ParentRobot != null, "ParentRobot != null"); + var container = ParentRobot.GetContainer(); + Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); var player = ParentRobot as Player; + Debug.Assert(player != null,"player != null"); foreach (var extractedMaterial in harvestedPlants) { var item = (Item)Factory.CreateWithRandomEID(extractedMaterial.Definition); + item.Owner = Owner; item.Quantity = extractedMaterial.Quantity; - container.AddItem(item, true); var extractedHarvestDefinition = extractedMaterial.Definition; @@ -145,73 +136,12 @@ private void DoHarvesting(IZone zone) player.Zone?.HarvestLogHandler.EnqueueHarvestLog(extractedHarvestDefinition, extractedQuantity); } - //everything went ok, save container container.Save(); - 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/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/RemoteControlledDrillerModule.cs b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs index 7b417a87d..f5294fb4c 100644 --- a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs @@ -39,6 +39,22 @@ public override void AcceptVisitor(IEntityVisitor visitor) } } + public override void UpdateProperty(AggregateField field) + { + switch (field) + { + case AggregateField.mining_amount_modifier: + case AggregateField.effect_mining_amount_modifier: + { + _miningAmountModifier.Update(); + + return; + } + } + + base.UpdateProperty(field); + } + public List Extract(MineralLayer layer, Point location, uint amount) { if (!layer.HasMineral(location)) diff --git a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs new file mode 100644 index 000000000..e2a923d24 --- /dev/null +++ b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs @@ -0,0 +1,111 @@ +using Perpetuum.Data; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; +using Perpetuum.Items; +using Perpetuum.Modules.ModuleProperties; +using Perpetuum.Players; +using Perpetuum.Zones.Beams; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; +using Perpetuum.Zones.Terrains; +using Perpetuum.Zones; +using System.Diagnostics; +using System.Transactions; + +namespace Perpetuum.Modules +{ + public class RemoteControlledHarvesterModule : GathererModule + { + private readonly PlantHarvester.Factory _plantHarvesterFactory; + private readonly HarvestingAmountModifierProperty _harverstingAmountModifier; + + public RemoteControlledHarvesterModule(PlantHarvester.Factory plantHarvesterFactory) : base(CategoryFlags.undefined, 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) + { + base.UpdateProperty(field); + + switch (field) + { + case AggregateField.harvesting_amount_modifier: + case AggregateField.effect_harvesting_amount_modifier: + { + _harverstingAmountModifier.Update(); + + break; + } + } + } + + protected override void OnAction() + { + var zone = Zone; + + if (zone == null) + { + return; + } + + DoHarvesting(zone); + } + + protected override int CalculateEp(int materialType) + { + return 0; + } + + private void DoHarvesting(IZone zone) + { + var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + + CreateBeam(terrainLock.Location, BeamState.AlignToTerrain); + + using (var scope = Db.CreateTransaction()) + { + using (new TerrainUpdateMonitor(zone)) + { + var plantInfo = zone.Terrain.Plants.GetValue(terrainLock.Location); + var amountModifier = _harverstingAmountModifier.GetValueByPlantType(plantInfo.type); + + IPlantHarvester plantHarvester = _plantHarvesterFactory(zone, amountModifier); + + var harvestedPlants = plantHarvester.HarvestPlant(terrainLock.Location); + + Debug.Assert(ParentRobot != null, "ParentRobot != null"); + + var container = ParentRobot.GetContainer(); + + Debug.Assert(container != null, "container != null"); + container.EnlistTransaction(); + + foreach (var extractedMaterial in harvestedPlants) + { + var item = (Item)Factory.CreateWithRandomEID(extractedMaterial.Definition); + + item.Owner = Owner; + item.Quantity = extractedMaterial.Quantity; + container.AddItem(item, true); + } + + container.Save(); + Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); + scope.Complete(); + } + } + } + } +} diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 781203405..ac1fc82a5 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -158,9 +158,14 @@ protected override void OnAction() { fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); } - else if (ammo.ED.Options.TurretType == TurretType.Mining) + else if (ammo.ED.Options.TurretType == TurretType.Mining || ammo.ED.Options.TurretType == TurretType.Harvesting) { - fieldTurret = (MiningTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + fieldTurret = (IndustrialTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + (fieldTurret as IndustrialTurret).SetTurretType(ammo.ED.Options.TurretType); + } + else + { + PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); } fieldTurret.Owner = this.ParentRobot.Eid; diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index 637379a33..01658a538 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -260,13 +260,16 @@ + + + @@ -675,11 +678,13 @@ + + - + @@ -689,7 +694,6 @@ - @@ -719,7 +723,7 @@ - + diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index a72c96fbc..00b4475b3 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -932,7 +932,7 @@ public bool IsHostile(Unit unit) internal virtual bool IsHostile(SentryTurret turret) { return false; } - internal virtual bool IsHostile(MiningTurret turret) { return false; } + internal virtual bool IsHostile(IndustrialTurret turret) { return false; } internal virtual bool IsHostile(Npc npc) { return false; } 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/MiningTurretIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/HarvestingTurretIndustrialAI.cs similarity index 64% rename from src/Perpetuum/Zones/NpcSystem/AI/MiningTurretIndustrialAI.cs rename to src/Perpetuum/Zones/NpcSystem/AI/HarvestingTurretIndustrialAI.cs index 9fdddab3c..f418cfa97 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/MiningTurretIndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/HarvestingTurretIndustrialAI.cs @@ -2,14 +2,14 @@ namespace Perpetuum.Zones.NpcSystem.AI { - public class MiningTurretIndustrialAI : StationaryIndustrialAI + public class HarvestingTurretIndustrialAI : StationaryIndustrialAI { - public MiningTurretIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + public HarvestingTurretIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() { return IndustrialPrimaryLockSelectionStrategySelector.Create() - .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, 1) + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.MostFertilePlantWithinOptimal, 1) .Build(); } } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs index e10b51307..aeebc7d68 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -15,12 +15,15 @@ public class IndustrialAI : BaseAI { private const int UpdateFrequency = 1650; private readonly IntervalTimer processIndustrialTargetsTimer = new IntervalTimer(UpdateFrequency); - private readonly IntervalTimer primarySelectTimer = new IntervalTimer(UpdateFrequency); + private IntervalTimer primarySelectTimer; private List moduleActivators; private TimeSpan industrialTargetsUpdateFrequency = TimeSpan.FromMilliseconds(UpdateFrequency); private IndustrialPrimaryLockSelectionStrategySelector stratSelector; - public IndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } + public IndustrialAI(SmartCreature smartCreature) : base(smartCreature) + { + primarySelectTimer = new IntervalTimer((this.smartCreature.ActiveModules.Max(x => x?.CycleTime.Milliseconds) ?? 0) + UpdateFrequency); + } public override void Enter() { @@ -44,7 +47,7 @@ public override void Update(TimeSpan time) protected virtual IndustrialPrimaryLockSelectionStrategySelector InitSelector() { return IndustrialPrimaryLockSelectionStrategySelector.Create() - .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, 9) + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTile, 9) .Build(); } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs deleted file mode 100644 index 0b251f583..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialTurretAI.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Perpetuum.Timers; -using Perpetuum.Zones.NpcSystem.TargettingStrategies; -using System; - -namespace Perpetuum.Zones.NpcSystem.AI -{ - public class IndustrialTurretAI : IndustrialAI - { - private readonly IntervalTimer updateFrequency = new IntervalTimer(2000); - - public IndustrialTurretAI(SmartCreature smartCreature) : base(smartCreature) { } - - protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() - { - return IndustrialPrimaryLockSelectionStrategySelector.Create() - .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, 10) - .Build(); - } - - public override void Update(TimeSpan time) - { - FindIndustrialTargets(time); - - base.Update(time); - } - - private void FindIndustrialTargets(TimeSpan time) - { - updateFrequency.Update(time); - - if (updateFrequency.Passed) - { - updateFrequency.Reset(); - smartCreature.LookingForIndustrialTargets(); - } - } - - protected override TimeSpan SetPrimaryDwellTime() - { - return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); - } - } -} 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 index c354c7a5e..1ac7538e2 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs @@ -25,7 +25,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private const double ENERGY_INJECTOR_THRESHOLD = 0.65; private const double ARMOR_REPAIR_THRESHOLD = 0.95; @@ -365,6 +366,19 @@ public void Visit(RemoteControlledDrillerModule module) module.State.SwitchTo(ModuleStateType.Oneshot); } + public void Visit(RemoteControlledHarvesterModule module) + { + var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + + if (lockTarget == null) + { + return; + } + + module.Lock = lockTarget; + module.State.SwitchTo(ModuleStateType.Oneshot); + } + private void TryActiveModule(LOSResult result, UnitLock primaryLock) { if (result.hit && !result.blockingFlags.HasFlag(BlockingFlags.Plant)) diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs index d117ab183..02d89f18f 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs @@ -1,35 +1,25 @@ using Perpetuum.Timers; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; using System; namespace Perpetuum.Zones.NpcSystem.AI { public class StationaryIndustrialAI : IndustrialAI { - private readonly IntervalTimer updateFrequency = new IntervalTimer(18000); + public readonly IntervalTimer UpdateFrequency = new IntervalTimer(18000); public StationaryIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } - public override void Update(TimeSpan time) + protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() { - FindIndustrialTargets(time); - - base.Update(time); + return IndustrialPrimaryLockSelectionStrategySelector.Create() + .WithStrategy(IndustrialPrimaryLockSelectionStrategy.RichestTile, 1) + .Build(); } protected override TimeSpan SetPrimaryDwellTime() { return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); } - - private void FindIndustrialTargets(TimeSpan time) - { - updateFrequency.Update(time); - - if (updateFrequency.Passed) - { - updateFrequency.Reset(); - smartCreature.LookingForIndustrialTargets(); - } - } } } diff --git a/src/Perpetuum/Zones/NpcSystem/Creature.cs b/src/Perpetuum/Zones/NpcSystem/Creature.cs index 2f9b1841d..b9a424829 100644 --- a/src/Perpetuum/Zones/NpcSystem/Creature.cs +++ b/src/Perpetuum/Zones/NpcSystem/Creature.cs @@ -114,19 +114,7 @@ public TerrainLock SelectOptimalLockIndustrialTargetFor(ActiveModule module) var primaryLock = locks.FirstOrDefault(l => l.Primary); - if (module.ED.AttributeFlags.PrimaryLockedTarget) - { - return primaryLock; - } - - var chance = FastRandom.NextDouble() <= PRIMARY_LOCK_CHANCE_FOR_SECONDARY_MODULE; - - if (primaryLock != null && chance) - { - return primaryLock; - } - - return locks.RandomElement(); + return primaryLock; } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index f01d63f2b..1e0e0e6ea 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -166,7 +166,7 @@ internal override bool IsHostile(SentryTurret turret) return true; } - internal override bool IsHostile(MiningTurret turret) + internal override bool IsHostile(IndustrialTurret turret) { return true; } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index f6726074b..d6f3e9e2d 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -19,6 +19,7 @@ using Perpetuum.Zones.Terrains.Materials; using Perpetuum.Zones.Terrains; using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.Terrains.Materials.Plants; namespace Perpetuum.Zones.NpcSystem { @@ -121,13 +122,13 @@ public void LookingForHostiles() } } - public void LookingForIndustrialTargets() + public void LookingForMiningTargets() { IndustrialValueManager.Clear(); var area = Zone.CreateArea(CurrentPosition, IndustrialScanRange); - var availableMaterialTypes = Zone.Terrain.GetAvailableMaterialTypes(); + var availableMaterialTypes = Zone.Terrain.GetAvailableMineralTypes(); foreach (MaterialType materialType in availableMaterialTypes) { @@ -161,6 +162,37 @@ public void LookingForIndustrialTargets() } } + public void LookingForHarvestingTargets() + { + IndustrialValueManager.Clear(); + + var area = Zone.CreateArea(CurrentPosition, IndustrialScanRange); + + var availablePlantTypes = Zone.Terrain.GetAvailablePlantTypes(); + + foreach (PlantType plantType in availablePlantTypes) + { + var plantsCounter = Zone.CountPlantsInArea(plantType, area); + + if (plantsCounter > 0) + { + var valuablePositions = Zone.GetPlantPositionsInArea(plantType, area) + .Select(x => Zone.FixZ(x)) + .Where(x => x.IsInRangeOf2D(this.CurrentPosition, this.BestCombatRange)); + + foreach (var valuablePosition in valuablePositions) + { + var plant = Zone.Terrain.Plants.GetValue(valuablePosition.intX, valuablePosition.intY); + + if (plant.material > 0) + { + IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Mineral, plant.material); + } + } + } + } + } + public void AddPseudoThreat(Unit hostile) { pseudoThreatManager.AddOrRefreshExisting(hostile); @@ -390,9 +422,16 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { AI.Push(new SentryTurretCombatAI(this)); } - else if (this is MiningTurret) + else if (this is IndustrialTurret) { - AI.Push(new MiningTurretIndustrialAI(this)); + if ((this as IndustrialTurret).TurretType == TurretType.Mining) + { + AI.Push(new MiningIndustrialTurretAI(this)); + } + else + { + AI.Push(new HarvestingIndustrialTurretAI(this)); + } } else if (IsStationary) { diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs index d23952cba..dfd2d9f2d 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialPrimaryLockSelectionStrategy.cs @@ -2,6 +2,7 @@ { public enum IndustrialPrimaryLockSelectionStrategy { - RichestTileWithinOptimal, + RichestTile, + MostFertilePlantWithinOptimal, } } diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs index 719b092dd..e421cb6d0 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs @@ -11,7 +11,7 @@ public static class IndustrialStrategies public static Dictionary All = new Dictionary() { - { IndustrialPrimaryLockSelectionStrategy.RichestTileWithinOptimal, TargetRichestTileWithinOptimal }, + { IndustrialPrimaryLockSelectionStrategy.RichestTile, TargetRichestTile }, }; public static IndustrialTargetSelectionStrategy GetStrategy(IndustrialPrimaryLockSelectionStrategy strategyType) @@ -31,13 +31,19 @@ public static bool TryInvokeStrategy(IndustrialPrimaryLockSelectionStrategy stra return industrialLockSelectionStrategy(smartCreature, locks); } - private static bool TargetRichestTileWithinOptimal(SmartCreature smartCreature, TerrainLock[] 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 - .Where(k => k.Location.IsInRangeOf3D(smartCreature.PositionWithHeight, smartCreature.BestCombatRange)) - .OrderByDescending(u => industrialTargets.Where(h => h.Position.ToString() == u.Location.ToString()).FirstOrDefault()?.IndustrialValue ?? 0) + //.Where(k => k.Location.IsInRangeOf2D(smartCreature.CurrentPosition, smartCreature.BestCombatRange)) + .OrderByDescending(u => industrialTargets.FirstOrDefault(h => h.Position.ToString() == u.Location.ToString())?.IndustrialValue ?? 0) .FirstOrDefault(); return TrySetPrimaryLock(smartCreature, mostHostileLock); diff --git a/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs similarity index 91% rename from src/Perpetuum/Zones/RemoteControl/MiningTurret.cs rename to src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs index 40598bc32..9f5c21c13 100644 --- a/src/Perpetuum/Zones/RemoteControl/MiningTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -12,10 +12,17 @@ namespace Perpetuum.Zones.RemoteControl { - public class MiningTurret : RemoteControlledTurret + public class IndustrialTurret : RemoteControlledTurret { - public MiningTurret() + public TurretType TurretType { get; private set; } + + public IndustrialTurret() + { + } + + public void SetTurretType(TurretType turretType) { + TurretType = turretType; } protected override bool IsHostileFor(Unit unit) diff --git a/src/Perpetuum/Zones/RemoteControl/TurretType.cs b/src/Perpetuum/Zones/RemoteControl/TurretType.cs index a978eef6c..64bd905cf 100644 --- a/src/Perpetuum/Zones/RemoteControl/TurretType.cs +++ b/src/Perpetuum/Zones/RemoteControl/TurretType.cs @@ -4,5 +4,6 @@ public enum TurretType { Sentry, Mining, + Harvesting, } } 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/TerrainExtensions.cs b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs index 2524a4bf2..77b5293f8 100644 --- a/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs +++ b/src/Perpetuum/Zones/Terrains/TerrainExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Threading; @@ -91,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++) @@ -314,7 +331,7 @@ public static MaterialType GetMaterialTypeAtPosition(this ITerrain terrain, Posi : (layer as MineralLayer).Type; } - public static MaterialType[] GetAvailableMaterialTypes(this ITerrain terrain) + public static MaterialType[] GetAvailableMineralTypes(this ITerrain terrain) { MaterialType[] materials = Enum.GetValues(typeof(MaterialType)) as MaterialType[]; @@ -323,6 +340,15 @@ public static MaterialType[] GetAvailableMaterialTypes(this ITerrain terrain) .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] From ba3be55ecaab938a80e1c95228ba7c3d2e94b96b Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 6 Dec 2023 01:30:45 +0500 Subject: [PATCH 424/457] - Industrial turrets are now giving ep and completing missions --- src/Perpetuum/Modules/DrillerModule.cs | 32 +++-- src/Perpetuum/Modules/HarvesterModule.cs | 12 +- .../Modules/RemoteControlledDrillerModule.cs | 120 +----------------- .../RemoteControlledHarvesterModule.cs | 81 +----------- .../Modules/RemoteControllerModule.cs | 2 +- .../Zones/NpcSystem/SmartCreature.cs | 27 ++-- .../RemoteControl/RemoteControlledTurret.cs | 8 ++ .../Zones/RemoteControl/SentryTurret.cs | 4 +- 8 files changed, 63 insertions(+), 223 deletions(-) diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index 7fcf1561d..2887f7685 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -14,6 +14,7 @@ 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; @@ -94,9 +95,9 @@ protected override void OnAction() protected override int CalculateEp(int materialType) { - var activeGathererModules = ParentRobot.ActiveModules.OfType() - .Where(m => m.State.Type != ModuleStateType.Idle) - .ToArray(); + var 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(); if (activeGathererModules.Length == 0) { @@ -124,18 +125,29 @@ protected override int CalculateEp(int materialType) return 0; } - private void DoExtractMinerals(IZone zone) + public void DoExtractMinerals(IZone zone) { var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); - MiningAmmo ammo = GetAmmo() as MiningAmmo; + MaterialType materialType; - if (ammo == null) + if (ParentRobot is RemoteControlledTurret) { - return; + materialType = zone.Terrain.GetMaterialTypeAtPosition(terrainLock.Location); + } + else + { + MiningAmmo ammo = GetAmmo() as MiningAmmo; + + if (ammo == null) + { + return; + } + + materialType = ammo.MaterialType; } - var materialInfo = _materialHelper.GetMaterialInfo(ammo.MaterialType); + var materialInfo = _materialHelper.GetMaterialInfo(materialType); CheckEnablerEffect(materialInfo); @@ -157,7 +169,9 @@ private void DoExtractMinerals(IZone zone) Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); - var player = ParentRobot as Player; + var player = ParentRobot is RemoteControlledTurret + ? (ParentRobot as RemoteControlledTurret).Player + : ParentRobot as Player; Debug.Assert(player != null,"player != null"); diff --git a/src/Perpetuum/Modules/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index 9cd20db62..3c124ad59 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -12,6 +12,7 @@ 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.Harvesters; @@ -57,7 +58,10 @@ public override void UpdateProperty(AggregateField field) protected override int CalculateEp(int materialType) { - var activeGathererModules = ParentRobot.ActiveModules.OfType().Where(m => m.State.Type != ModuleStateType.Idle).ToArray(); + + var 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) { @@ -93,7 +97,7 @@ protected override void OnAction() ConsumeAmmo(); } - private void DoHarvesting(IZone zone) + public void DoHarvesting(IZone zone) { var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); @@ -117,7 +121,9 @@ private void DoHarvesting(IZone zone) Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); - var player = ParentRobot as Player; + var player = ParentRobot is RemoteControlledTurret + ? (ParentRobot as RemoteControlledTurret).Player + : ParentRobot as Player; Debug.Assert(player != null,"player != null"); diff --git a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs index f5294fb4c..88c26c7a0 100644 --- a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs @@ -1,34 +1,14 @@ -using Perpetuum.Data; -using Perpetuum.EntityFramework; +using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Items; -using Perpetuum.Zones.Beams; -using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.Terrains.Materials.Minerals; using Perpetuum.Zones.Terrains.Materials; -using Perpetuum.Zones; -using System.Collections.Generic; -using System.Diagnostics; -using System.Transactions; -using Perpetuum.Zones.Terrains; -using System.Drawing; -using Perpetuum.Modules.ModuleProperties; namespace Perpetuum.Modules { - public class RemoteControlledDrillerModule : GathererModule + public class RemoteControlledDrillerModule : DrillerModule { - private readonly RareMaterialHandler _rareMaterialHandler; - private readonly MaterialHelper _materialHelper; - private readonly ItemProperty _miningAmountModifier; - public RemoteControlledDrillerModule(RareMaterialHandler rareMaterialHandler, MaterialHelper materialHelper) - : base(CategoryFlags.undefined, true) + : base(CategoryFlags.undefined, rareMaterialHandler, materialHelper) { - _rareMaterialHandler = rareMaterialHandler; - _materialHelper = materialHelper; - _miningAmountModifier = new MiningAmountModifierProperty(this); - AddProperty(_miningAmountModifier); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -39,36 +19,6 @@ public override void AcceptVisitor(IEntityVisitor visitor) } } - public override void UpdateProperty(AggregateField field) - { - switch (field) - { - case AggregateField.mining_amount_modifier: - case AggregateField.effect_mining_amount_modifier: - { - _miningAmountModifier.Update(); - - return; - } - } - - base.UpdateProperty(field); - } - - public List Extract(MineralLayer layer, Point location, uint amount) - { - if (!layer.HasMineral(location)) - { - return new List(); - } - - var extractor = new MineralExtractor(location, amount, _materialHelper); - - layer.AcceptVisitor(extractor); - - return new List(extractor.Items); - } - protected override void OnAction() { var zone = Zone; @@ -78,69 +28,5 @@ protected override void OnAction() DoExtractMinerals(zone); } } - - protected override int CalculateEp(int materialType) - { - return 0; - } - - private void DoExtractMinerals(IZone zone) - { - var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); - var materialType = zone.Terrain.GetMaterialTypeAtPosition(terrainLock.Location); - var materialInfo = _materialHelper.GetMaterialInfo(materialType); - - CheckEnablerEffect(materialInfo); - - var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); - var materialAmount = materialInfo.Amount * _miningAmountModifier.Value; - var 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()) - { - Debug.Assert(ParentRobot != null, "ParentRobot != null"); - - var container = ParentRobot.GetContainer(); - - Debug.Assert(container != null, "container != null"); - container.EnlistTransaction(); - - foreach (var material in extractedMaterials) - { - var item = (Item)Factory.CreateWithRandomEID(material.Definition); - - item.Owner = Owner; - item.Quantity = material.Quantity; - container.AddItem(item, true); - } - - //save container - container.Save(); - Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); - scope.Complete(); - } - } - - private void CheckEnablerEffect(MaterialInfo materialInfo) - { - if (!Zone.Configuration.Terraformable) - { - return; - } - - if (!materialInfo.EnablerExtensionRequired) - { - return; - } - - var containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); - - containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); - } } } diff --git a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs index e2a923d24..a621f2844 100644 --- a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs @@ -1,30 +1,18 @@ -using Perpetuum.Data; -using Perpetuum.EntityFramework; +using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Items; using Perpetuum.Modules.ModuleProperties; -using Perpetuum.Players; -using Perpetuum.Zones.Beams; -using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; -using Perpetuum.Zones.Terrains; -using Perpetuum.Zones; -using System.Diagnostics; -using System.Transactions; namespace Perpetuum.Modules { - public class RemoteControlledHarvesterModule : GathererModule + public class RemoteControlledHarvesterModule : HarvesterModule { + private const int MAX_EP_PER_DAY = 1440; private readonly PlantHarvester.Factory _plantHarvesterFactory; private readonly HarvestingAmountModifierProperty _harverstingAmountModifier; - public RemoteControlledHarvesterModule(PlantHarvester.Factory plantHarvesterFactory) : base(CategoryFlags.undefined, true) + public RemoteControlledHarvesterModule(PlantHarvester.Factory plantHarvesterFactory) : base(CategoryFlags.undefined, plantHarvesterFactory) { - _plantHarvesterFactory = plantHarvesterFactory; - _harverstingAmountModifier = new HarvestingAmountModifierProperty(this); - AddProperty(_harverstingAmountModifier); - cycleTime.AddEffectModifier(AggregateField.effect_harvesting_cycle_time_modifier); } public override void AcceptVisitor(IEntityVisitor visitor) @@ -35,22 +23,6 @@ public override void AcceptVisitor(IEntityVisitor visitor) } } - public override void UpdateProperty(AggregateField field) - { - base.UpdateProperty(field); - - switch (field) - { - case AggregateField.harvesting_amount_modifier: - case AggregateField.effect_harvesting_amount_modifier: - { - _harverstingAmountModifier.Update(); - - break; - } - } - } - protected override void OnAction() { var zone = Zone; @@ -62,50 +34,5 @@ protected override void OnAction() DoHarvesting(zone); } - - protected override int CalculateEp(int materialType) - { - return 0; - } - - private void DoHarvesting(IZone zone) - { - var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); - - CreateBeam(terrainLock.Location, BeamState.AlignToTerrain); - - using (var scope = Db.CreateTransaction()) - { - using (new TerrainUpdateMonitor(zone)) - { - var plantInfo = zone.Terrain.Plants.GetValue(terrainLock.Location); - var amountModifier = _harverstingAmountModifier.GetValueByPlantType(plantInfo.type); - - IPlantHarvester plantHarvester = _plantHarvesterFactory(zone, amountModifier); - - var harvestedPlants = plantHarvester.HarvestPlant(terrainLock.Location); - - Debug.Assert(ParentRobot != null, "ParentRobot != null"); - - var container = ParentRobot.GetContainer(); - - Debug.Assert(container != null, "container != null"); - container.EnlistTransaction(); - - foreach (var extractedMaterial in harvestedPlants) - { - var item = (Item)Factory.CreateWithRandomEID(extractedMaterial.Definition); - - item.Owner = Owner; - item.Quantity = extractedMaterial.Quantity; - container.AddItem(item, true); - } - - container.Save(); - Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); - scope.Complete(); - } - } - } } } diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index ac1fc82a5..a3b4c3d86 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -168,7 +168,7 @@ protected override void OnAction() PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); } - fieldTurret.Owner = this.ParentRobot.Eid; + fieldTurret.SetPlayer(this.ParentRobot as Player); HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); UseRemoteChannel(fieldTurret); diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index d6f3e9e2d..e830d7752 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -20,6 +20,7 @@ using Perpetuum.Zones.Terrains; using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.Terrains.Materials.Plants; +using Perpetuum.Players; namespace Perpetuum.Zones.NpcSystem { @@ -327,10 +328,10 @@ public bool IsInAggroRange(Unit target) public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) { - //if (hostile.IsPlayer()) - //{ - // BossInfo?.OnAggro(hostile as Player); - //} + if (hostile.IsPlayer()) + { + BossInfo?.OnAggro(hostile as Player); + } threatManager.GetOrAddHostile(hostile).AddThreat(threat); @@ -347,17 +348,17 @@ public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) return; } - //var multipliedThreat = Threat.Multiply(threat, 0.5); + var multipliedThreat = Threat.Multiply(threat, 0.5); - //foreach (var member in @group.Members) - //{ - // if (member == this) - // { - // continue; - // } + foreach (var member in @group.Members) + { + if (member == this) + { + continue; + } - // member.AddThreat(hostile, multipliedThreat, false); - //} + member.AddThreat(hostile, multipliedThreat, false); + } } ///
diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs index 1621f2e5b..ea144bd38 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs @@ -1,5 +1,6 @@ using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones.Beams; using Perpetuum.Zones.NpcSystem; @@ -16,6 +17,8 @@ public class RemoteControlledTurret : SmartCreature public event RemoteChannelEventHandler RemoteChannelDeactivated; + public Player Player { get; private set; } + public double RemoteChannelBandwidthUsage { get { return rcBandwidthUsage.Value; } @@ -39,6 +42,11 @@ public override void Initialize() public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; + public void SetPlayer(Player player) + { + Player = player; + } + protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 9c183aa38..fc5b55ebb 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -1,6 +1,4 @@ -using Perpetuum.ExportedTypes; -using Perpetuum.Groups.Corporations; -using Perpetuum.Items; +using Perpetuum.Groups.Corporations; using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; From 9070cead33e23876aa1a9aff91e565d27525d44d Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 6 Dec 2023 04:49:31 +0500 Subject: [PATCH 425/457] - Removed remote_control effect and related logic as no longer needed - Sentry turrets are now make yellow bots aggro on owner - Sentry turrets are now giving ep for killing npc of any type - Sentry turrets owner is now getting loot from boss correctly - Sentry turrets are now can be used for combat missions - Industrial turrets are no longer trying to mine/harvest empty tiles when there is nothing left in range - Both combat and industrial turrets are now managing targets more properly, unlocking targets with zero threat / value --- src/Perpetuum.ExportedTypes/EffectCategory.cs | 1 - src/Perpetuum.ExportedTypes/EffectType.cs | 1 - src/Perpetuum/ErrorCodes.cs | 1 + src/Perpetuum/Modules/ActiveModule.cs | 388 ++++++++++-------- .../RemoteControlledHarvesterModule.cs | 5 - .../Modules/RemoteControllerModule.cs | 23 +- src/Perpetuum/Players/Player.cs | 120 +++--- src/Perpetuum/Units/TagHelper.cs | 8 + src/Perpetuum/Units/Unit.cs | 2 - .../NpcSystem/AI/BodyPullThreatHelper.cs | 3 +- src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 16 +- .../Zones/NpcSystem/AI/IndustrialAI.cs | 27 +- .../Zones/NpcSystem/SmartCreature.cs | 9 +- .../Zones/RemoteControl/SentryTurret.cs | 8 +- 14 files changed, 332 insertions(+), 280 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/EffectCategory.cs b/src/Perpetuum.ExportedTypes/EffectCategory.cs index 9a2853981..836fa5ec6 100644 --- a/src/Perpetuum.ExportedTypes/EffectCategory.cs +++ b/src/Perpetuum.ExportedTypes/EffectCategory.cs @@ -55,6 +55,5 @@ public enum EffectCategory : long effcat_zone_effects = 0x0000100000000000, // OPP: New Zone effects effcat_environmental_effects = 0x0000200000000000, // OPP: New Zone effects effcat_mine_detection = 0x0000400000000000, // OPP: Mine detection - effcat_remote_control = 0x0000800000000000, // OPP: Remote control } } diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index f0b1e3cf0..2847d19f1 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -122,6 +122,5 @@ public enum EffectType effect_gamma_t3 = 119, effect_gamma_tc = 120, effect_mine_detector = 121, - effect_remote_control = 122, } } diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index aa3b4f6c4..ff7455ef7 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -712,5 +712,6 @@ public enum ErrorCodes MaxExpiringBasePerZonePerCorpReached, RespecTokenIsNotReadyYet, MaxBandwidthExceed, + RemoteControlledTurretInRange, } } diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 6334ec250..aed3a5d33 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -13,6 +13,7 @@ using Perpetuum.Zones.Beams; using Perpetuum.Zones.Locking; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains; namespace Perpetuum.Modules @@ -27,6 +28,52 @@ public abstract partial class ActiveModule : Module private readonly CategoryFlags _ammoCategoryFlags; + public Lock Lock + { + [CanBeNull] + get { return _lock; } + set + { + if (_lock != null) + { + _lock.Changed -= LockChangedHandler; + } + + _lock = value; + + if (_lock != null) + { + _lock.Changed += LockChangedHandler; + } + } + } + + public bool IsRanged { get; private set; } + + public override double Volume + { + get + { + var volume = base.Volume; + var ammo = GetAmmo(); + + if (ammo != null) + { + volume += ammo.Volume; + } + + return volume; + } + } + + public TimeSpan CycleTime => TimeSpan.FromMilliseconds(cycleTime.Value); + + public double CoreUsage => coreUsage.Value; + + public double OptimalRange => optimalRange.Value; + + public double Falloff => falloff.Value; + protected ActiveModule(CategoryFlags ammoCategoryFlags,bool ranged = false) { IsRanged = ranged; @@ -46,6 +93,10 @@ protected ActiveModule(CategoryFlags ammoCategoryFlags,bool ranged = false) _ammoCategoryFlags = ammoCategoryFlags; } + protected ActiveModule(bool ranged) : this(CategoryFlags.undefined, ranged) + { + } + public override void Initialize() { InitState(); @@ -53,58 +104,26 @@ public override void Initialize() base.Initialize(); } - - protected ActiveModule(bool ranged) : this(CategoryFlags.undefined,ranged) - { - } - - public TimeSpan CycleTime => TimeSpan.FromMilliseconds(cycleTime.Value); - - public double CoreUsage => coreUsage.Value; - - public double OptimalRange => optimalRange.Value; - - public double Falloff => falloff.Value; - - public Lock Lock - { - [CanBeNull] private get { return _lock; } - set - { - if (_lock != null) - _lock.Changed -= LockChangedHandler; - - _lock = value; - - if (_lock != null) - _lock.Changed += LockChangedHandler; - } - } - - public bool IsRanged { get; private set; } - public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) + { base.AcceptVisitor(visitor); + } } - private void LockChangedHandler(Lock @lock) + public override void UpdateRelatedProperties(AggregateField field) { - if (State.Type == ModuleStateType.Idle || State.Type == ModuleStateType.AmmoLoad) - return; - - var shutdown = @lock.State == LockState.Disabled || (ED.AttributeFlags.PrimaryLockedTarget && !@lock.Primary); - if (!shutdown) - return; + var ammo = GetAmmo(); - State.SwitchTo(ModuleStateType.Shutdown); - _lock = null; + ammo?.UpdateRelatedProperties(field); + base.UpdateRelatedProperties(field); } public bool IsInRange(Position position) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); + return !IsRanged || ParentRobot.IsInRangeOf3D(position,OptimalRange + Falloff); } @@ -114,44 +133,43 @@ public void ForceUpdate() SendAmmoUpdatePacketToPlayer(); } - private BeamType GetBeamType() + public void Update(TimeSpan time) { + _states.Update(time); + } + + public override void UpdateAllProperties() + { + base.UpdateAllProperties(); + var ammo = GetAmmo(); - return ammo != null ? BeamHelper.GetBeamByDefinition(ammo.Definition) : BeamHelper.GetBeamByDefinition(Definition); + + ammo?.UpdateAllProperties(); } - private void SendModuleStateToPlayer() + public override void Unequip(Container container) { - var state = State; + UnequipAmmoToContainer(container); + base.Unequip(container); + } - var player = ParentRobot as Player; - if (player == null) - return; + public override Dictionary ToDictionary() + { + var result = base.ToDictionary(); - 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); + result.Add(k.ammoCategoryFlags, (long)_ammoCategoryFlags); - var timed = state as ITimedModuleState; - if (timed == null) - { - packet.AppendInt(0); - packet.AppendInt(0); - } - else + var ammo = GetAmmo(); + + if (ammo == null) { - packet.AppendInt((int)timed.Timer.Interval.TotalMilliseconds); - packet.AppendInt((int)timed.Timer.Elapsed.TotalMilliseconds); + return result; } - player.Session.SendPacket(packet); - } + result.Add(k.ammo, ammo.ToDictionary()); + result.Add(k.ammoQuantity, ammo.Quantity); - public void Update(TimeSpan time) - { - _states.Update(time); + return result; } protected abstract void OnAction(); @@ -160,7 +178,6 @@ protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock uni { if (parentPlayer != null) { - // pvp ellenorzes (unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError(); } } @@ -168,15 +185,20 @@ protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock uni protected Lock GetLock() { var 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; + if (parentPlayer is null && ParentRobot is RemoteControlledTurret) + { + parentPlayer = (ParentRobot as RemoteControlledTurret).Player; + } + if (ED.AttributeFlags.OffensiveModule) { HandleOffensivePVPCheck(parentPlayer, unitLockTarget); @@ -190,8 +212,7 @@ protected Lock GetLock() } else { - var terrainLockTarget = currentLock as TerrainLock; - if (terrainLockTarget != null) + if (currentLock is TerrainLock terrainLockTarget) { IsInRange(terrainLockTarget.Location).ThrowIfFalse(ErrorCodes.TargetOutOfRange); } @@ -200,14 +221,6 @@ 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); @@ -218,35 +231,6 @@ 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); @@ -257,35 +241,6 @@ protected int CreateBeam(Position location, BeamState beamState, int duration, d 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; - } - protected override void OnUpdateProperty(AggregateField field) { switch (field) @@ -294,6 +249,7 @@ protected override void OnUpdateProperty(AggregateField field) case AggregateField.effect_core_usage_gathering_modifier: { coreUsage.Update(); + break; } case AggregateField.cycle_time: @@ -301,6 +257,7 @@ protected override void OnUpdateProperty(AggregateField field) case AggregateField.effect_gathering_cycle_time_modifier: { cycleTime.Update(); + break; } case AggregateField.optimal_range: @@ -310,11 +267,13 @@ protected override void OnUpdateProperty(AggregateField field) case AggregateField.effect_missile_range_modifier: { optimalRange.Update(); + break; } case AggregateField.falloff: { falloff.Update(); + break; } } @@ -322,53 +281,61 @@ protected override void OnUpdateProperty(AggregateField field) base.OnUpdateProperty(field); } - public override void Unequip(Container container) - { - UnequipAmmoToContainer(container); - base.Unequip(container); - } - protected double ModifyValueByOptimalRange(Unit target,double value) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); var distance = ParentRobot.GetDistance(target); + if (distance <= OptimalRange) + { return value; + } if (distance > OptimalRange + Falloff) + { return 0.0; + } 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); + 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)); + return losResult; } protected bool LOSCheckAndCreateBeam(Unit target) { var result = GetLineOfSight(target); + if (result.hit) { var beamState = (result.blockingFlags != BlockingFlags.Undefined) ? BeamState.AlignToTerrain : BeamState.Hit; + CreateBeam(result.position, beamState); + return false; } CreateBeam(target, BeamState.Hit); + return true; } @@ -377,52 +344,141 @@ protected void OnError(ErrorCodes error) SendModuleErrorToPlayer(error); } - private void SendModuleErrorToPlayer(ErrorCodes error) + private void LockChangedHandler(Lock @lock) { - var player = ParentRobot as Player; - if (player == null) + if (State.Type == ModuleStateType.Idle || State.Type == ModuleStateType.AmmoLoad) + { + return; + } + + var shutdown = @lock.State == LockState.Disabled || (ED.AttributeFlags.PrimaryLockedTarget && !@lock.Primary); + + if (!shutdown) + { return; + } + + State.SwitchTo(ModuleStateType.Shutdown); + _lock = null; + } + + private BeamType GetBeamType() + { + var ammo = GetAmmo(); + + return ammo != null + ? BeamHelper.GetBeamByDefinition(ammo.Definition) + : BeamHelper.GetBeamByDefinition(Definition); + } + + private void SendModuleStateToPlayer() + { + var state = State; + + if (!(ParentRobot is Player player)) + { + 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); + + 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(); + var delay = 0; + var 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"); + + 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; } - public override double Volume + private int CreateBeam(Position location, BeamState beamState, int duration, double bulletTime, int visibility) { - get + var delay = 0; + var 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"); + + 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; } - 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); + var packet = new CombatLogPacket(error, this, _lock); + + player.Session.SendPacket(packet); } } } \ No newline at end of file diff --git a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs index a621f2844..9f2ad2af5 100644 --- a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs @@ -1,16 +1,11 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; -using Perpetuum.Modules.ModuleProperties; using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; namespace Perpetuum.Modules { public class RemoteControlledHarvesterModule : HarvesterModule { - private const int MAX_EP_PER_DAY = 1440; - private readonly PlantHarvester.Factory _plantHarvesterFactory; - private readonly HarvestingAmountModifierProperty _harverstingAmountModifier; - public RemoteControlledHarvesterModule(PlantHarvester.Factory plantHarvesterFactory) : base(CategoryFlags.undefined, plantHarvesterFactory) { } diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index a3b4c3d86..7a683a61a 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -134,7 +134,12 @@ protected override void OnAction() .OfType() .WithinRange(zone.FixZ(targetPosition), SentryTurretDeployRange) .Any() - .ThrowIfTrue(ErrorCodes.BlobEmitterInRange); + .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); + zone.Units + .OfType() + .WithinRange(zone.FixZ(targetPosition), SentryTurretDeployRange) + .Any() + .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); var r = zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); @@ -168,6 +173,7 @@ protected override void OnAction() PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); } + fieldTurret.Owner = this.Owner; fieldTurret.SetPlayer(this.ParentRobot as Player); HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); @@ -190,22 +196,7 @@ protected override void OnAction() fieldTurret.AddToZone(zone, position, ZoneEnterType.Default, beamBuilder); - var effectBuilder = this.ParentRobot.NewEffectBuilder(); - - SetupEffect(effectBuilder); - - effectBuilder - .WithToken(effectToken) - .WithDuration(despawnTime); - - this.ParentRobot.ApplyEffect(effectBuilder); - ConsumeAmmo(); } - - protected void SetupEffect(EffectBuilder effectBuilder) - { - effectBuilder.SetType(EffectType.effect_remote_control); - } } } diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index b3d27a6ae..d74251a33 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -69,6 +69,11 @@ public sealed class Player : Robot, IBlobableUnit, IBlobEmitter public const int ARKHE_REQUEST_TIMER_MINUTES_PVP = 3; public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 1; + private bool HasAggressorEffect + { + get { return EffectHandler.ContainsEffect(EffectType.effect_aggressor); } + } + public long CorporationEid { get; set; } public IZoneSession Session { get; private set; } @@ -201,6 +206,7 @@ public override void OnUpdateToDb() } var zone = Zone; + if (zone == null || States.Dead) { return; @@ -256,7 +262,6 @@ public void SendModuleProcessError(Module module, ErrorCodes error) packet.AppendByte((byte)module.ParentComponent.Type); packet.AppendByte((byte)module.Slot); packet.AppendInt((int)error); - Session.SendPacket(packet); } @@ -276,6 +281,7 @@ public void RemoveInvulnerableEffect() public void ApplyTeleportSicknessEffect() { var zone = Zone; + if (zone == null || zone is TrainingZone) { return; @@ -426,7 +432,6 @@ public override ItemPropertyModifier GetPropertyModifier(AggregateField field) public override void OnAggression(Unit victim) { base.OnAggression(victim); - AddInCombatWith(victim); if (victim is ITaggable taggable) @@ -447,7 +452,6 @@ public override void OnAggression(Unit victim) } victimPlayer.Session.CancelLogout(); - ApplyPvPEffect(); if (IsInSameCorporation(victimPlayer)) @@ -479,7 +483,6 @@ public void SendInitSelf() var zone = Zone; Debug.Assert(zone != null, "zone != null"); - Session.SendPacket(EnterPacketBuilder); Session.SendTerrainData(); zone.SendBeamsToPlayer(this, GridDistricts.All); @@ -492,9 +495,7 @@ public void SendInitSelf() { Session.SendPacket(visibility.Target.EnterPacketBuilder); - var robot = visibility.Target as Robot; - - if (robot == null) + if (!(visibility.Target is Robot robot)) { continue; } @@ -524,7 +525,7 @@ public void WriteFQLog(string message) { LogType = LogType.Info, Tag = "FQ", - Message = $"{InfoString} - {message}" + Message = $"{InfoString} - {message}", }; Logger.Log(e); @@ -582,31 +583,26 @@ public static Player LoadPlayerAndAddToZone(IZone zone, Character character) spawnPosition = zone.FixZ(spawnPosition); - // keresunk neki valami jo poziciot var finder = new ClosestWalkablePositionFinder(zone, spawnPosition, player); var validPosition = finder.FindOrThrow(); - // 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(); - - // elmentjuk player.Save(); - // csak akkor rakjuk ki ha volt rendes commit Transaction.Current.OnCommited(() => { dockingBase?.LeaveChannel(character); - player.CorporationEid = character.CorporationEid; zone.SetGang(player); - player.AddToZone(zone, validPosition, zoneEnterType); player.ApplyInvulnerableEffect(); }); scope.Complete(); + return player; } } @@ -644,8 +640,11 @@ public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) 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(); + Message.Builder + .SetCommand(Commands.AlarmStart) + .WithData(data) + .ToCharacter(Character) + .Send(); } public void SendEndProgressBar(Unit unit, bool success = true) @@ -714,6 +713,40 @@ public override void UpdateVisibilityOf(Unit target) target.UpdatePlayerVisibility(this); } + public void SetCombatState(bool state) + { + States.Combat = state; + combatTimer.Reset(); + + if (state) + { + Session.ResetLogoutTimer(); + } + } + + public void AddInCombatWith(Unit enemy) + { + SetCombatState(true); + + var enemyPlayer = enemy as Player; + + enemyPlayer?.SetCombatState(true); + } + + public bool IsInSameCorporation(Player player) + { + return (CorporationEid == player.CorporationEid) && !IsInDefaultCorporation(); + } + + public bool IsUnitPVPAggro(Unit unit) + { + return unit is MobileTeleport || + unit is IPBSObject || + unit is WallHealer || + unit is ProximityDeviceBase || + (unit is BlobEmitterUnit b && b.IsPlayerSpawned); + } + protected override void OnDead(Unit killer) { HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); @@ -837,9 +870,7 @@ protected override void OnLockStateChanged(Lock @lock) if (@lock.State == LockState.Locked) { - 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()); @@ -890,12 +921,9 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) { 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 p = DynamicProperties.GetProperty(k.pvpRemaining); @@ -913,7 +941,6 @@ protected override void OnRemovedFromZone(IZone zone) { Session.SendPacket(ExitPacketBuilder); zone.SendPacketToGang(Gang, new GangUpdatePacketBuilder(Visibility.Invisible, this)); - check.StopAndDispose(); if (!States.LocalTeleport) @@ -931,7 +958,6 @@ protected override void OnUpdate(TimeSpan time) movement.Update(time); blobHandler.Update(time); MissionHandler.Update(time); - combatLogger?.Update(time); despawnHelper?.Update(time, this); } @@ -965,16 +991,6 @@ private void OnUnitBroadcastPacket(Unit sender, Packet packet) Session.SendPacket(packet); } - private bool HasAggressorEffect - { - get { return EffectHandler.ContainsEffect(EffectType.effect_aggressor); } - } - - private void ApplyAggressorEffect() - { - ApplyEffect(NewEffectBuilder().SetType(EffectType.effect_aggressor)); - } - private void UpdateCombat(TimeSpan time) { if (!States.Combat) @@ -992,26 +1008,6 @@ private void UpdateCombat(TimeSpan time) SetCombatState(false); } - private void SetCombatState(bool state) - { - States.Combat = state; - combatTimer.Reset(); - - if (state) - { - Session.ResetLogoutTimer(); - } - } - - private void AddInCombatWith(Unit enemy) - { - SetCombatState(true); - - var enemyPlayer = enemy as Player; - - enemyPlayer?.SetCombatState(true); - } - private void Reload() { var container = GetContainer(); @@ -1068,25 +1064,11 @@ private void ApplyHighwayEffect(bool apply) } } - private bool IsInSameCorporation(Player player) - { - return (CorporationEid == player.CorporationEid) && !IsInDefaultCorporation(); - } - private void SendError(ErrorCodes error) { Session.SendPacket(new ErrorPacketBuilder(error)); } - private bool IsUnitPVPAggro(Unit unit) - { - return unit is MobileTeleport || - unit is IPBSObject || - unit is WallHealer || - unit is ProximityDeviceBase || - (unit is BlobEmitterUnit b && b.IsPlayerSpawned); - } - private Task HandlePlayerDeadAsync(IZone zone, Unit killer) { return Task.Run(() => HandlePlayerDead(zone, killer)); 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.cs b/src/Perpetuum/Units/Unit.cs index 00b4475b3..9dc9c3186 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -252,8 +252,6 @@ public bool IsInvulnerable public bool HasNoTeleportWhilePVP => EffectHandler.Effects.Any(e => e.PropertyModifiers.Any(p => p.Field == AggregateField.pvp_no_teleport)); - public bool HasRemoteControlEffect => EffectHandler.ContainsEffect(EffectType.effect_remote_control); - public Position WorldPosition { get { return Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index d63707021..5a085d5c6 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -21,8 +21,7 @@ public BodyPullThreatHelper(SmartCreature smartCreature) public void Visit(Player player) { if (smartCreature.Behavior.Type != BehaviorType.Aggressive && - smartCreature.Behavior.Type != BehaviorType.RemoteControlled && - !player.HasRemoteControlEffect) + smartCreature.Behavior.Type != BehaviorType.RemoteControlled) { return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index b1136850b..36e47d658 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -184,10 +184,14 @@ protected bool TryMakeFreeLockSlotFor(Hostile hostile) 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(h => h != hostile) + .SkipWhile(x => x != hostile) .Skip(1) - .Select(h => this.smartCreature.GetLockByUnit(h.Unit)) + .Select(x => this.smartCreature.GetLockByUnit(x.Unit)) .LastOrDefault(); if (weakestLock == null) @@ -217,9 +221,9 @@ protected Hostile GetPrimaryOrMostHatedHostile() private void SetLockForHostile(Hostile hostile) { var mostHated = GetPrimaryOrMostHatedHostile() == hostile; - var l = this.smartCreature.GetLockByUnit(hostile.Unit); + var combatLock = this.smartCreature.GetLockByUnit(hostile.Unit); - if (l == null) + if (combatLock == null) { if (TryMakeFreeLockSlotFor(hostile)) { @@ -228,9 +232,9 @@ private void SetLockForHostile(Hostile hostile) } else { - if (mostHated && !l.Primary) + if (mostHated && !combatLock.Primary) { - this.smartCreature.SetPrimaryLock(l.Id); + this.smartCreature.SetPrimaryLock(combatLock.Id); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs index aeebc7d68..f39ee79b2 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -69,6 +69,7 @@ protected void UpdatePrimaryTarget(TimeSpan time) if (primarySelectTimer.Passed) { var success = SelectPrimaryTarget(); + SetPrimaryUpdateDelay(success); } } @@ -150,6 +151,16 @@ protected bool IsAttackable(Hostile hostile) 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()) @@ -172,6 +183,10 @@ protected bool TryMakeFreeLockSlotFor(IndustrialTarget industrialTarget) return true; } + this.smartCreature.IndustrialValueManager.IndustrialTargets + .Where(x => x.IndustrialValue == 0) + .ForEach(x => this.smartCreature.GetLockByPosition(x.Position).Cancel()); + var weakestLock = this.smartCreature.IndustrialValueManager.IndustrialTargets .SkipWhile(h => h != industrialTarget) .Skip(1) @@ -188,7 +203,7 @@ protected bool TryMakeFreeLockSlotFor(IndustrialTarget industrialTarget) return true; } - protected IndustrialTarget GetPrimaryOrMostValuableIndustrialTarget() + protected IndustrialTarget GetMostValuableIndustrialTarget() { var primaryTarget = this.smartCreature.IndustrialValueManager.IndustrialTargets .Where(h => h.Position == (this.smartCreature.GetPrimaryLock() as TerrainLock)?.Location) @@ -204,10 +219,10 @@ protected IndustrialTarget GetPrimaryOrMostValuableIndustrialTarget() private void SetLockForIndustrialTarget(IndustrialTarget industrialTarget) { - var mostValuable = GetPrimaryOrMostValuableIndustrialTarget() == industrialTarget; - var l = this.smartCreature.GetLockByPosition(industrialTarget.Position); + var mostValuable = GetMostValuableIndustrialTarget() == industrialTarget; + var industrialLock = this.smartCreature.GetLockByPosition(industrialTarget.Position); - if (l == null) + if (industrialLock == null) { if (TryMakeFreeLockSlotFor(industrialTarget)) { @@ -216,9 +231,9 @@ private void SetLockForIndustrialTarget(IndustrialTarget industrialTarget) } else { - if (mostValuable && !l.Primary) + if (mostValuable && !industrialLock.Primary) { - this.smartCreature.SetPrimaryLock(l.Id); + this.smartCreature.SetPrimaryLock(industrialLock.Id); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index e830d7752..e8d5a010a 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -303,7 +303,6 @@ private int CalculateCombatRange() .Select(module => module.OptimalRange) .Concat(new[] { MaxTargetingRange }) .Min(); - range *= BestComnatRangeModifier; range = Math.Max(3, range); @@ -312,10 +311,10 @@ private int CalculateCombatRange() private int CalculateMaxCombatRange() { - double range = ActiveModules.Where(m => m.IsRanged) - .Select(module => (int)(module.OptimalRange + module.Falloff)) - .Max(); - + double range = ActiveModules + .Where(m => m.IsRanged) + .Select(module => (int)(module.OptimalRange + module.Falloff)) + .Max(); range = Math.Max(3, range); return (int)range; diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index fc5b55ebb..e59a1e442 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -3,6 +3,7 @@ using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.NpcSystem; +using System; namespace Perpetuum.Zones.RemoteControl { @@ -23,7 +24,7 @@ protected override bool IsHostileFor(Unit unit) public override bool IsHostile(Player player) { - if (Owner == player.Eid) + if (Player == player) { return false; } @@ -31,6 +32,11 @@ public override bool IsHostile(Player player) return IsHostileCorporation(player.CorporationEid); } + public override void OnAggression(Unit victim) + { + this.Player.OnAggression(victim); + } + internal override bool IsHostile(Npc npc) { return true; From ea70da1a8c1279d26a0bb7c8664263ece1cc88d3 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Fri, 8 Dec 2023 22:39:50 +0500 Subject: [PATCH 426/457] - Fixed mess after merge --- src/Perpetuum/Items/RespecToken.cs | 41 ++++++++++++++++ src/Perpetuum/Items/SparkTeleportDevice.cs | 14 +----- src/Perpetuum/Robots/Robot.cs | 49 ------------------- .../Teleports/ISparkTeleportRepository.cs | 2 - 4 files changed, 42 insertions(+), 64 deletions(-) create mode 100644 src/Perpetuum/Items/RespecToken.cs 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 index cdedfe94d..3ab15b923 100644 --- a/src/Perpetuum/Items/SparkTeleportDevice.cs +++ b/src/Perpetuum/Items/SparkTeleportDevice.cs @@ -1,16 +1,4 @@ -using Perpetuum.Accounting; -using Perpetuum.Accounting.Characters; -using Perpetuum.Common.Loggers.Transaction; -using Perpetuum.Data; -using Perpetuum.Host.Requests; -using Perpetuum.Players; -using Perpetuum.Robots; -using Perpetuum.Services.Sparks.Teleports; -using System; -using System.Collections.Generic; -using System.Transactions; - -namespace Perpetuum.Items +namespace Perpetuum.Items { public class SparkTeleportDevice : Item { diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 42a259d7e..8d3a06098 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -189,55 +189,6 @@ public override IDictionary GetDebugInfo() return info; } - public bool IsSelected => RobotHelper.IsSelected(this); - - public override double Health - { - get - { - if (IsRepackaged) - { - return base.Health; - } - - return ArmorMax > 0.0 && Armor > 0.0 - ? Armor.Ratio(ArmorMax) * 100 - : base.Health; - } - } - - 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); - } - - var builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); - OnBroadcastPacket(builder.ToProxy()); - } - - protected override void OnUpdate(TimeSpan time) - { - base.OnUpdate(time); - - _lockHandler.Update(time); - - foreach (var robotComponent in RobotComponents) - { - robotComponent.Update(time); - } - } - public void FullArmorRepair() { DynamicProperties.Update(k.armor, 1.0); diff --git a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs index a4a48c9b7..fd46cbd48 100644 --- a/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs +++ b/src/Perpetuum/Services/Sparks/Teleports/ISparkTeleportRepository.cs @@ -11,7 +11,5 @@ public interface ISparkTeleportRepository : IRepository IEnumerable GetAllByCharacter(Character character); IEnumerable GetAllByDockingBase(DockingBase dockingBase); - - SparkTeleport GetCommon(int id); } } From 82e1fddce80be714bac9faa7eed43d0d94aed24d Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 3 Jan 2024 03:08:08 +0500 Subject: [PATCH 427/457] - Industrial turrets now affected by zone effects - Turrets deployment now have graphic effects - Industrial turrets now "thinking" lesser before starting to mine --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 5 ----- src/Perpetuum/Modules/RemoteControllerModule.cs | 10 ++++++---- .../Zones/Effects/ZoneEffects/ZoneEffectHandler.cs | 3 ++- .../Zones/NpcSystem/AI/StationaryIndustrialAI.cs | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 8c6163f96..5c9274158 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -186,11 +186,6 @@ using TrashItems = Perpetuum.RequestHandlers.TrashItems; using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; -using Perpetuum.Services.Strongholds; -using Perpetuum.Zones.NpcSystem.Presences.RandomExpiringPresence; -using Perpetuum.Zones.NpcSystem.Presences.ExpiringStaticPresence; -using Perpetuum.Zones.NpcSystem.Presences.GrowingPresences; -using Perpetuum.Items.Helpers; using Perpetuum.Zones.LandMines; using Perpetuum.Zones.RemoteControl; diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 7a683a61a..87a25b7aa 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -189,12 +189,14 @@ protected override void OnAction() var finder = new ClosestWalkablePositionFinder(zone, targetPosition); var position = finder.FindOrThrow(); - var beamBuilder = Beam.NewBuilder() - .WithType(BeamType.deploy_device_01) - .WithPosition(targetPosition) + var deployBeamBuilder = Beam.NewBuilder() + .WithType(BeamType.dock_in) + .WithSource(fieldTurret.Player) + .WithTarget(fieldTurret) + .WithState(BeamState.Hit) .WithDuration(TimeSpan.FromSeconds(5)); - fieldTurret.AddToZone(zone, position, ZoneEnterType.Default, beamBuilder); + fieldTurret.AddToZone(zone, position, ZoneEnterType.Deploy, deployBeamBuilder); ConsumeAmmo(); } diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs index 730996f5d..c6042940c 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs @@ -1,6 +1,7 @@ using Perpetuum.Log; using Perpetuum.Players; using Perpetuum.Units; +using Perpetuum.Zones.RemoteControl; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -72,7 +73,7 @@ private IEnumerable GetEffects() private Func CanApplyEffect(ZoneEffect effect) { - return u => !u.EffectHandler.ContainsEffect(effect.Effect) && (!effect.PlayerOnly || u is Player); + return u => !u.EffectHandler.ContainsEffect(effect.Effect) && (!effect.PlayerOnly || u is Player || u is RemoteControlledTurret); } public void OnEnterZone(Unit unit) diff --git a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs index 02d89f18f..2fc130358 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/StationaryIndustrialAI.cs @@ -6,7 +6,7 @@ namespace Perpetuum.Zones.NpcSystem.AI { public class StationaryIndustrialAI : IndustrialAI { - public readonly IntervalTimer UpdateFrequency = new IntervalTimer(18000); + public readonly IntervalTimer UpdateFrequency = new IntervalTimer(5000); public StationaryIndustrialAI(SmartCreature smartCreature) : base(smartCreature) { } @@ -19,7 +19,7 @@ protected override IndustrialPrimaryLockSelectionStrategySelector InitSelector() protected override TimeSpan SetPrimaryDwellTime() { - return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); } } } From b4748d4a309a6f08d478d0ea593d63eb8038a295 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 3 Jan 2024 14:52:53 +0500 Subject: [PATCH 428/457] Turrets are now affected by nexuses --- src/Perpetuum/Zones/Effects/GangEffect.cs | 11 +++++++++-- src/Perpetuum/Zones/ZoneExtensions.Gang.cs | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Zones/Effects/GangEffect.cs b/src/Perpetuum/Zones/Effects/GangEffect.cs index 520782e4d..ace6d2e85 100644 --- a/src/Perpetuum/Zones/Effects/GangEffect.cs +++ b/src/Perpetuum/Zones/Effects/GangEffect.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.Linq; using Perpetuum.Groups.Gangs; using Perpetuum.Players; using Perpetuum.Units; +using Perpetuum.Zones.RemoteControl; namespace Perpetuum.Zones.Effects { @@ -15,10 +17,11 @@ protected override void OnTick() if (Owner != Source) { var gang = ((Player)Owner).Gang; - // ha nincs mar gangben vagy a forras mar nincs gangben + if (gang == null || !gang.IsMember((Player)Source)) { OnRemoved(); + return; } } @@ -29,7 +32,11 @@ protected override void OnTick() protected override IEnumerable GetTargets(IZone zone) { var player = (Player)Owner; - return zone.GetGangMembers(player.Gang).WithinRange(Owner.CurrentPosition, Radius); + var gangMembers = zone.GetGangMembers(player.Gang); + var alliedTurrets = zone.GetAlliedTurretsByPlayers(new[] { player }.Union(gangMembers)); + var alliedUnits = (gangMembers).Union(alliedTurrets); + + return alliedUnits.WithinRange(Owner.CurrentPosition, Radius); } } diff --git a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs index de593ee1a..e14b5c193 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 RemoteControlledTurret[0] + : zone.Units + .Where(x => x is RemoteControlledTurret) + .Join( + players, + turret => ((RemoteControlledTurret)turret).Player, + player => player, + (turret, player) => (RemoteControlledTurret)turret); + } + public static void SendPacketToGang(this IZone zone, Gang gang, IBuilder packetBuidler, long exceptMemberEid = 0L) { var packet = packetBuidler.Build(); From f5b49a808c80d78377e8c7b94cb2fcfdcddf961b Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Thu, 4 Jan 2024 00:15:28 +0500 Subject: [PATCH 429/457] - Fixed bug with nexuses being applied to turrets - Industrial turrets now use optimal range from modules to calculate distance (was hardcoded) --- src/Perpetuum/Zones/Effects/GangEffect.cs | 7 +++- .../Zones/NpcSystem/AI/AggressorAI.cs | 6 ++-- src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 2 +- .../Zones/NpcSystem/AI/IndustrialAI.cs | 2 +- src/Perpetuum/Zones/NpcSystem/Npc.cs | 2 +- .../Zones/NpcSystem/SmartCreature.cs | 33 +++++++------------ .../TargettingStrategies/CombatStrategies.cs | 2 +- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/Perpetuum/Zones/Effects/GangEffect.cs b/src/Perpetuum/Zones/Effects/GangEffect.cs index ace6d2e85..960c6753c 100644 --- a/src/Perpetuum/Zones/Effects/GangEffect.cs +++ b/src/Perpetuum/Zones/Effects/GangEffect.cs @@ -16,7 +16,11 @@ protected override void OnTick() { if (Owner != Source) { - var gang = ((Player)Owner).Gang; + var gang = Owner is Player player + ? player.Gang + : Owner is RemoteControlledTurret turret + ? turret.Player.Gang + : null; if (gang == null || !gang.IsMember((Player)Source)) { @@ -24,6 +28,7 @@ protected override void OnTick() return; } + } base.OnTick(); diff --git a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs index 508d081e9..48e00e819 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs @@ -74,7 +74,7 @@ private void UpdateHostile(TimeSpan time) var findNewTargetPosition = false; - if (!smartCreature.IsInRangeOf3D(mostHated.Unit, smartCreature.BestCombatRange)) + if (!smartCreature.IsInRangeOf3D(mostHated.Unit, smartCreature.BestActionRange)) { findNewTargetPosition = true; } @@ -136,7 +136,7 @@ private Task> FindNewAttackPositionAsync(Unit hostile) private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) { - var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestCombatRange - 1).ToPoint(); + var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestActionRange - 1).ToPoint(); smartCreature.StopMoving(); @@ -203,7 +203,7 @@ private bool IsValidAttackPosition(Unit hostile, Point position) { var position3 = smartCreature.Zone.FixZ(position.ToPosition()).AddToZ(smartCreature.Height); - if (!hostile.CurrentPosition.IsInRangeOf3D(position3, smartCreature.BestCombatRange)) + if (!hostile.CurrentPosition.IsInRangeOf3D(position3, smartCreature.BestActionRange)) { return false; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index 36e47d658..53b6386f0 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -260,7 +260,7 @@ private bool IsLockValidTarget(UnitLock unitLock) return false; } - return unitLock.Target.GetDistance(this.smartCreature) < this.smartCreature.MaxCombatRange; + return unitLock.Target.GetDistance(this.smartCreature) < this.smartCreature.MaxActionRange; } private UnitLock[] GetValidLocks() diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs index f39ee79b2..499a04405 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -245,7 +245,7 @@ private bool IsLockValidTarget(TerrainLock industrialLock) return false; } - return industrialLock.Location.IsInRangeOf2D(this.smartCreature.PositionWithHeight, this.smartCreature.MaxIndustrialRange); + return industrialLock.Location.IsInRangeOf2D(this.smartCreature.PositionWithHeight, this.smartCreature.MaxActionRange); } private TerrainLock[] GetValidLocks() diff --git a/src/Perpetuum/Zones/NpcSystem/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 1e0e0e6ea..44cd2435d 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -62,7 +62,7 @@ public override IDictionary GetDebugInfo() info.Add("homeDistance", homeDistance); info.Add("coreMax", CoreMax); info.Add("coreCurrent", Core); - info.Add("bestCombatRange", BestCombatRange); + info.Add("bestCombatRange", BestActionRange); var currentAI = AI.Current; diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index e8d5a010a..545b3946c 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -29,15 +29,14 @@ public class SmartCreature : Creature private const double AggroRange = 30; private const double BestComnatRangeModifier = 0.9; private const double BaseCallForHelpArmorThreshold = 0.2; - private const int IndustrialScanRange = 30; private readonly ThreatManager threatManager; private readonly PseudoThreatManager pseudoThreatManager; private readonly IndustrialValueManager industrialValueManager; 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 maxCombatRange; - private Lazy optimalCombatRange; + private Lazy maxActionRange; + private Lazy optimalActionRange; private TimeSpan lastHelpCalled; [CanBeNull] @@ -66,14 +65,14 @@ public IndustrialValueManager IndustrialValueManager get { return industrialValueManager; } } - public int BestCombatRange + public int BestActionRange { - get { return this.optimalCombatRange.Value; } + get { return this.optimalActionRange.Value; } } - public int MaxCombatRange + public int MaxActionRange { - get { return this.maxCombatRange.Value; } + get { return this.maxActionRange.Value; } } public bool IsInHomeRange @@ -81,14 +80,6 @@ public bool IsInHomeRange get { return CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); } } - public int MaxIndustrialRange - { - get - { - return IndustrialScanRange; - } - } - public ISmartCreatureGroup Group { get { return group; } @@ -127,7 +118,7 @@ public void LookingForMiningTargets() { IndustrialValueManager.Clear(); - var area = Zone.CreateArea(CurrentPosition, IndustrialScanRange); + var area = Zone.CreateArea(CurrentPosition, BestActionRange); var availableMaterialTypes = Zone.Terrain.GetAvailableMineralTypes(); @@ -150,7 +141,7 @@ public void LookingForMiningTargets() .GetPositions() .Where(x => mineralLayer.HasMineral(x)) .Select(x=>Zone.FixZ(x)) - .Where(x => x.IsInRangeOf3D(this.PositionWithHeight, this.BestCombatRange)); + .Where(x => x.IsInRangeOf3D(this.PositionWithHeight, this.BestActionRange)); foreach (var valuablePosition in valuablePositions) { @@ -167,7 +158,7 @@ public void LookingForHarvestingTargets() { IndustrialValueManager.Clear(); - var area = Zone.CreateArea(CurrentPosition, IndustrialScanRange); + var area = Zone.CreateArea(CurrentPosition, BestActionRange); var availablePlantTypes = Zone.Terrain.GetAvailablePlantTypes(); @@ -179,7 +170,7 @@ public void LookingForHarvestingTargets() { var valuablePositions = Zone.GetPlantPositionsInArea(plantType, area) .Select(x => Zone.FixZ(x)) - .Where(x => x.IsInRangeOf2D(this.CurrentPosition, this.BestCombatRange)); + .Where(x => x.IsInRangeOf2D(this.CurrentPosition, this.BestActionRange)); foreach (var valuablePosition in valuablePositions) { @@ -206,12 +197,12 @@ public void SetGroup(ISmartCreatureGroup group) public void RecalculateOptimalCombatRange() { - optimalCombatRange = new Lazy(CalculateCombatRange); + optimalActionRange = new Lazy(CalculateCombatRange); } public void RecalculateMaxCombatRange() { - maxCombatRange = new Lazy(CalculateMaxCombatRange); + maxActionRange = new Lazy(CalculateMaxCombatRange); } protected override void OnPropertyChanged(ItemProperty property) diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs index 61c479828..1b0184215 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs @@ -51,7 +51,7 @@ private static bool TargetWithinOptimal(SmartCreature smartCreature, UnitLock[] return TrySetPrimaryLock( smartCreature, locks - .Where(k => k.Target.GetDistance(smartCreature) < smartCreature.BestCombatRange) + .Where(k => k.Target.GetDistance(smartCreature) < smartCreature.BestActionRange) .RandomElement()); } From 4560619baa1246009aaef2d9e18e0c48ee33ad9d Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 10 Jan 2024 04:03:57 +0500 Subject: [PATCH 430/457] - Fixed an issue with game crash on turret deployment (I hope) - Players are now not considered enemies if they are in squad with turret owner (needs re-checking) - Refactored remote control mechanics a little bit --- .../Modules/RemoteControllerModule.cs | 78 ++++++++++--------- src/Perpetuum/Players/Player.cs | 4 +- src/Perpetuum/Robots/Robot.cs | 1 - .../Zones/RemoteControl/BandwidthHandler.cs | 2 +- .../RemoteControl/RemoteControlledTurret.cs | 38 +++++---- .../RemoteControl/RemoteControlledUnit.cs | 8 +- .../Zones/RemoteControl/SentryTurret.cs | 21 +++-- src/Perpetuum/Zones/ZoneConfiguration.cs | 2 +- 8 files changed, 85 insertions(+), 69 deletions(-) diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 87a25b7aa..f5bffc60f 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -1,15 +1,17 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Log; using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Units; using Perpetuum.Zones; using Perpetuum.Zones.Beams; -using Perpetuum.Zones.Effects; using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.RemoteControl; using System; using System.Linq; @@ -19,10 +21,9 @@ namespace Perpetuum.Modules public class RemoteControllerModule : ActiveModule { private const int SentryTurretHeight = 7; - private const double SentryTurretDeployRange = 2; + private const double TurretDeployRange = 2; private readonly ModuleProperty bandwidthMax; private BandwidthHandler bandwidthHandler; - protected readonly EffectToken effectToken = EffectToken.NewToken(); public double BandwidthMax { @@ -36,9 +37,6 @@ public RemoteControllerModule(CategoryFlags ammoCategoryFlags) : base(ammoCatego bandwidthMax = new ModuleProperty(this, AggregateField.remote_control_bandwidth_max); this.AddProperty(bandwidthMax); - var remoteControlDamageModifier = new ModuleProperty(this, AggregateField.remote_control_damage_modifier); - this.AddProperty(remoteControlDamageModifier); - InitBandwidthHandler(this); } @@ -52,17 +50,21 @@ public void SyncRemoteChannels() bandwidthHandler.Update(); } + /* [CanBeNull] public RemoteChannel GetRemoteChannel(long channelId) { - return bandwidthHandler.GetRemoteChannel(channelId); + //return bandwidthHandler.GetRemoteChannel(channelId); } + */ + /* [CanBeNull] public RemoteChannel GetRemoteChannelByUnit(Unit unit) { - return bandwidthHandler.GetRemoteChannelByUnit(unit); + //return bandwidthHandler.GetRemoteChannelByUnit(unit); } + */ public bool HasFreeBandwidthFor(RemoteControlledUnit unit) { @@ -76,10 +78,12 @@ public void UseRemoteChannel(RemoteControlledTurret turret) bandwidthHandler.Update(); } + /* public void UseRemoteChannel(RemoteChannel newChannel) { bandwidthHandler.UseRemoteChannel(newChannel); } + */ public override void AcceptVisitor(IEntityVisitor visitor) { @@ -103,13 +107,6 @@ protected override void OnAction() SyncRemoteChannels(); - var zone = Zone; - - if (zone == null) - { - return; - } - Position? lockPosition; var myLock = GetLock(); @@ -130,18 +127,14 @@ protected override void OnAction() } Position targetPosition = lockPosition.Value; - zone.Units - .OfType() - .WithinRange(zone.FixZ(targetPosition), SentryTurretDeployRange) + + Zone.Units + .OfType() + .WithinRange(Zone.FixZ(targetPosition), TurretDeployRange) .Any() .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); - zone.Units - .OfType() - .WithinRange(zone.FixZ(targetPosition), SentryTurretDeployRange) - .Any() - .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); - - var r = zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); + + var r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); if (r.hit) { @@ -152,7 +145,14 @@ protected override void OnAction() var ammo = GetAmmo() as RemoteControlledUnit; - if (this.ParentRobot is Player player) + HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); + + var player = this.ParentRobot is Player robotAsPlayer + ? robotAsPlayer + : null; + + + if (player != null) { ammo.CheckEnablerExtensionsAndThrowIfFailed(player.Character, ErrorCodes.ExtensionLevelMismatchTerrain); } @@ -173,22 +173,29 @@ protected override void OnAction() PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); } - fieldTurret.Owner = this.Owner; - fieldTurret.SetPlayer(this.ParentRobot as Player); + if (player != null) + { + fieldTurret.SetPlayer(player); + } + + fieldTurret.Owner = this.ParentRobot.Owner; + fieldTurret.Behavior = Behavior.Create(BehaviorType.RemoteControlled); + fieldTurret.SetBandwidthUsage(ammo.RemoteChannelBandwidthUsage); - HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); UseRemoteChannel(fieldTurret); var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); - var despawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); - - fieldTurret.DespawnTime = despawnTime; - + fieldTurret.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); fieldTurret.SetGroup(bandwidthHandler); - var finder = new ClosestWalkablePositionFinder(zone, targetPosition); + var finder = new ClosestWalkablePositionFinder(Zone, targetPosition); var position = finder.FindOrThrow(); + + fieldTurret.HomePosition = position; + fieldTurret.HomeRange = 30; + fieldTurret.CallForHelp = true; + var deployBeamBuilder = Beam.NewBuilder() .WithType(BeamType.dock_in) .WithSource(fieldTurret.Player) @@ -196,7 +203,8 @@ protected override void OnAction() .WithState(BeamState.Hit) .WithDuration(TimeSpan.FromSeconds(5)); - fieldTurret.AddToZone(zone, position, ZoneEnterType.Deploy, deployBeamBuilder); + fieldTurret.AddToZone(Zone, position, ZoneEnterType.Default, deployBeamBuilder); + Logger.Info($"[Remote Control] - spawned turret {fieldTurret.Eid} of type {ammo.ED.Options.TurretType} owned by {fieldTurret.Owner} represented by player {fieldTurret.Player} at {targetPosition}"); ConsumeAmmo(); } diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index d74251a33..a60580474 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -36,6 +36,7 @@ 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; @@ -744,7 +745,8 @@ public bool IsUnitPVPAggro(Unit unit) unit is IPBSObject || unit is WallHealer || unit is ProximityDeviceBase || - (unit is BlobEmitterUnit b && b.IsPlayerSpawned); + (unit is BlobEmitterUnit b && b.IsPlayerSpawned) || + unit is RemoteControlledTurret; } protected override void OnDead(Unit killer) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 8d3a06098..b85b9398b 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -3,7 +3,6 @@ using System.Linq; using Perpetuum.Accounting.Characters; using Perpetuum.Builders; -using Perpetuum.Common; using Perpetuum.Containers; using Perpetuum.Containers.SystemContainers; using Perpetuum.EntityFramework; diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index e0a1e64cf..546866635 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -18,7 +18,7 @@ public class BandwidthHandler : ISmartCreatureGroup private readonly ConcurrentQueue deactivatedChannels = new ConcurrentQueue(); private int dirty; - public string Name => owner.GetCharacter().Nick; + public string Name => owner.ParentRobot.GetCharacter().Nick; public IEnumerable Members => channels.Select(x => x.Turret); diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs index ea144bd38..69e2a5c53 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs @@ -1,4 +1,5 @@ -using Perpetuum.ExportedTypes; +using Perpetuum.Data; +using Perpetuum.ExportedTypes; using Perpetuum.Items; using Perpetuum.Players; using Perpetuum.Units; @@ -13,30 +14,31 @@ public class RemoteControlledTurret : SmartCreature { private const double SentryTurretCallForHelpArmorThreshold = 0.8; private UnitDespawnHelper despawnHelper; - private ItemProperty rcBandwidthUsage = ItemProperty.None; + private ItemProperty remoteChannelBandwidthUsage = ItemProperty.None; public event RemoteChannelEventHandler RemoteChannelDeactivated; public Player Player { get; private set; } - public double RemoteChannelBandwidthUsage - { - get { return rcBandwidthUsage.Value; } - } + public double RemoteChannelBandwidthUsage { get; private set; } public TimeSpan DespawnTime { - set { despawnHelper = UnitDespawnHelper.Create(this, value); } + set + { + despawnHelper = UnitDespawnHelper.Create(this, value); + } } - public override void Initialize() - { - rcBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); - AddProperty(rcBandwidthUsage); - Behavior = Behavior.Create(BehaviorType.RemoteControlled); + //public override void Initialize() + //{ + // remoteChannelBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); + // /* + // AddProperty(rcBandwidthUsage); + // */ - base.Initialize(); - } + // base.Initialize(); + //} public override bool IsStationary => true; @@ -47,6 +49,11 @@ public void SetPlayer(Player player) Player = player; } + public void SetBandwidthUsage(double value) + { + RemoteChannelBandwidthUsage = value; + } + protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); @@ -61,12 +68,13 @@ protected override void OnRemovedFromZone(IZone zone) protected override void OnDead(Unit killer) { + /* Zone.CreateBeam( BeamType.arbalest_wreck, builder => builder .WithPosition(CurrentPosition) .WithState(BeamState.Hit)); - + */ base.OnDead(killer); } } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs index a32ee4b87..0319fc4ad 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledUnit.cs @@ -6,19 +6,19 @@ namespace Perpetuum.Zones.RemoteControl { public class RemoteControlledUnit : Ammo { - private ItemProperty rcBandwidthUsage = ItemProperty.None; + private ItemProperty remoteChannelBandwidthUsage = ItemProperty.None; public override void Initialize() { - rcBandwidthUsage = new AmmoProperty(this, AggregateField.remote_control_bandwidth_usage); - AddProperty(rcBandwidthUsage); + remoteChannelBandwidthUsage = new AmmoProperty(this, AggregateField.remote_control_bandwidth_usage); + AddProperty(remoteChannelBandwidthUsage); base.Initialize(); } public double RemoteChannelBandwidthUsage { - get { return rcBandwidthUsage.Value; } + get { return remoteChannelBandwidthUsage.Value; } } } } diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index e59a1e442..b262a5d26 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -3,7 +3,6 @@ using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.NpcSystem; -using System; namespace Perpetuum.Zones.RemoteControl { @@ -24,12 +23,17 @@ protected override bool IsHostileFor(Unit unit) public override bool IsHostile(Player player) { - if (Player == player) + if (Player != null && Player == player) { return false; } - return IsHostileCorporation(player.CorporationEid); + if (Player.Gang != null && Player.Gang.IsMember(player.Character)) + { + return false; + } + + return IsHostilePlayer(player.Eid); } public override void OnAggression(Unit victim) @@ -50,16 +54,11 @@ protected override void UpdateUnitVisibility(Unit target) } } - private bool IsHostileCorporation(long corporationEid) + private bool IsHostilePlayer(long playerEid) { - if (DefaultCorporationDataCache.IsCorporationDefault(corporationEid)) - { - return true; - } - - var standing = standingHandler.GetStanding(Owner, corporationEid); + var standing = standingHandler.GetStanding(Owner, playerEid); return StandingLimit >= standing; } } -} +} \ No newline at end of file diff --git a/src/Perpetuum/Zones/ZoneConfiguration.cs b/src/Perpetuum/Zones/ZoneConfiguration.cs index f99d61f95..2a47eec6b 100644 --- a/src/Perpetuum/Zones/ZoneConfiguration.cs +++ b/src/Perpetuum/Zones/ZoneConfiguration.cs @@ -104,7 +104,7 @@ public sealed class ZoneConfiguration 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; } From e7653b85b8fabab59bb90f785ae936af23125d4e Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 10 Jan 2024 23:21:45 +0500 Subject: [PATCH 431/457] - Little code cleanup --- src/Perpetuum/Modules/RemoteControllerModule.cs | 1 + .../RemoteControl/RemoteControlledTurret.cs | 17 ----------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index f5bffc60f..a7f48a9b4 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -194,6 +194,7 @@ protected override void OnAction() fieldTurret.HomePosition = position; fieldTurret.HomeRange = 30; + fieldTurret.Orientation = FastRandom.NextInt(0, 3) * 0.25; fieldTurret.CallForHelp = true; var deployBeamBuilder = Beam.NewBuilder() diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs index 69e2a5c53..0efa12bce 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs @@ -30,16 +30,6 @@ public TimeSpan DespawnTime } } - //public override void Initialize() - //{ - // remoteChannelBandwidthUsage = new UnitProperty(this, AggregateField.remote_control_bandwidth_usage); - // /* - // AddProperty(rcBandwidthUsage); - // */ - - // base.Initialize(); - //} - public override bool IsStationary => true; public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; @@ -68,13 +58,6 @@ protected override void OnRemovedFromZone(IZone zone) protected override void OnDead(Unit killer) { - /* - Zone.CreateBeam( - BeamType.arbalest_wreck, - builder => builder - .WithPosition(CurrentPosition) - .WithState(BeamState.Hit)); - */ base.OnDead(killer); } } From 72ac76f7ade3ab26a6b530f5a67f5c3c3f8f05c2 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Fri, 12 Jan 2024 22:33:06 +0500 Subject: [PATCH 432/457] - Combat drones. Not yet refactored --- .../PerpetuumBootstrapper.cs | 5 + src/Perpetuum.ExportedTypes/CategoryFlags.cs | 16 +- src/Perpetuum/Modules/ActiveModule.cs | 4 +- src/Perpetuum/Modules/DrillerModule.cs | 6 +- src/Perpetuum/Modules/HarvesterModule.cs | 4 +- .../Modules/RemoteControllerModule.cs | 146 +++++------ src/Perpetuum/Perpetuum.csproj | 10 +- src/Perpetuum/Players/Player.cs | 2 +- src/Perpetuum/Robots/Robot.cs | 7 - src/Perpetuum/Units/Unit.cs | 12 + src/Perpetuum/Zones/Effects/GangEffect.cs | 2 +- .../Effects/ZoneEffects/ZoneEffectHandler.cs | 2 +- .../Zones/NpcSystem/AI/AggressorAI.cs | 23 -- src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs | 11 + .../Zones/NpcSystem/AI/Behaviors/Behavior.cs | 6 +- .../NpcSystem/AI/Behaviors/BehaviorType.cs | 3 +- .../AI/Behaviors/RemoteControlledBehavior.cs | 9 - .../RemoteControlledDroneBehavior.cs | 9 + .../RemoteControlledTurretBehavior.cs | 9 + .../NpcSystem/AI/BodyPullThreatHelper.cs | 6 +- src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 11 +- .../AI/CombatDrones/AttackCombatDroneAI.cs | 239 ++++++++++++++++++ .../AI/CombatDrones/EscortCombatDroneAI.cs | 80 ++++++ .../AI/CombatDrones/GuardCombatDroneAI.cs | 44 ++++ src/Perpetuum/Zones/NpcSystem/AI/Node.cs | 28 ++ src/Perpetuum/Zones/NpcSystem/Npc.cs | 7 +- .../Zones/NpcSystem/SmartCreature.cs | 11 +- .../CombatPrimaryLockSelectionStrategy.cs | 1 + .../TargettingStrategies/CombatStrategies.cs | 15 ++ .../ThreatManagement/PseudoThreatManager.cs | 5 + .../Zones/RemoteControl/BandwidthHandler.cs | 4 +- .../Zones/RemoteControl/CombatDrone.cs | 121 +++++++++ .../Zones/RemoteControl/IndustrialTurret.cs | 2 +- .../Zones/RemoteControl/RemoteChannel.cs | 6 +- ...dTurret.cs => RemoteControlledCreature.cs} | 2 +- .../Zones/RemoteControl/SentryTurret.cs | 2 +- .../Zones/RemoteControl/TurretType.cs | 1 + src/Perpetuum/Zones/ZoneExtensions.Gang.cs | 10 +- 38 files changed, 727 insertions(+), 154 deletions(-) delete mode 100644 src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledDroneBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledTurretBehavior.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/EscortCombatDroneAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/GuardCombatDroneAI.cs create mode 100644 src/Perpetuum/Zones/NpcSystem/AI/Node.cs create mode 100644 src/Perpetuum/Zones/RemoteControl/CombatDrone.cs rename src/Perpetuum/Zones/RemoteControl/{RemoteControlledTurret.cs => RemoteControlledCreature.cs} (96%) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 5c9274158..96972f9ba 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -847,6 +847,7 @@ 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(); RegisterRobot(); @@ -1119,6 +1120,9 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); @@ -1257,6 +1261,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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); diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index f2554d13a..47cf939b9 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -107,10 +107,15 @@ public enum CategoryFlags : long cf_construction_ammo_main = 0x000000000000110A, cf_construction_ammo = 0x000000000001110A, cf_wall_builder_ammo = 0x000000000002110A, - cf_remote_controlled_units = 0x000000000000200A, - cf_sentry_turret_units = 0x000000000010200A, - cf_mining_turret_units = 0x000000000020200A, - cf_harvesting_turret_units = 0x000000000030200A, + 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, @@ -187,6 +192,7 @@ public enum CategoryFlags : long 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, @@ -263,7 +269,6 @@ public enum CategoryFlags : long cf_small_industrial_aoe = 0x00000000010A070F, cf_medium_industrial_aoe = 0x00000000020A070F, cf_large_industrial_aoe = 0x00000000030A070F, - cf_remote_controllers = 0x00000000000B070F, cf_sentry_turret_guns = 0x00000000000C070F, cf_terraforming_equipment = 0x000000000000080F, cf_small_terraformer = 0x000000000001080F, @@ -563,6 +568,7 @@ public enum CategoryFlags : long cf_sentry_turrets = 0x0000000000000D92, cf_mining_turrets = 0x0000000000000E92, cf_harvesting_turrets = 0x0000000000000F92, + cf_combat_drones = 0x0000000000001092, cf_production_items = 0x0000000000000094, cf_research_kits = 0x0000000000000194, diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index aed3a5d33..0bab55f56 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -194,9 +194,9 @@ protected Lock GetLock() var parentPlayer = ParentRobot as Player; - if (parentPlayer is null && ParentRobot is RemoteControlledTurret) + if (parentPlayer is null && ParentRobot is RemoteControlledCreature) { - parentPlayer = (ParentRobot as RemoteControlledTurret).Player; + parentPlayer = (ParentRobot as RemoteControlledCreature).Player; } if (ED.AttributeFlags.OffensiveModule) diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index 2887f7685..69cbcfc03 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -131,7 +131,7 @@ public void DoExtractMinerals(IZone zone) MaterialType materialType; - if (ParentRobot is RemoteControlledTurret) + if (ParentRobot is RemoteControlledCreature) { materialType = zone.Terrain.GetMaterialTypeAtPosition(terrainLock.Location); } @@ -169,8 +169,8 @@ public void DoExtractMinerals(IZone zone) Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); - var player = ParentRobot is RemoteControlledTurret - ? (ParentRobot as RemoteControlledTurret).Player + var player = ParentRobot is RemoteControlledCreature + ? (ParentRobot as RemoteControlledCreature).Player : ParentRobot as Player; Debug.Assert(player != null,"player != null"); diff --git a/src/Perpetuum/Modules/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index 3c124ad59..327e7acb6 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -121,8 +121,8 @@ public void DoHarvesting(IZone zone) Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); - var player = ParentRobot is RemoteControlledTurret - ? (ParentRobot as RemoteControlledTurret).Player + var player = ParentRobot is RemoteControlledCreature + ? (ParentRobot as RemoteControlledCreature).Player : ParentRobot as Player; Debug.Assert(player != null,"player != null"); diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index a7f48a9b4..95db3e02e 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -10,7 +10,6 @@ using Perpetuum.Zones.Finders; using Perpetuum.Zones.Finders.PositionFinders; using Perpetuum.Zones.Locking.Locks; -using Perpetuum.Zones.NpcSystem; using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.RemoteControl; using System; @@ -22,6 +21,8 @@ public class RemoteControllerModule : ActiveModule { private const int SentryTurretHeight = 7; private const double TurretDeployRange = 2; + private const double SpawnRangeMin = 2; + private const double SpawnRangeMax = 5; private readonly ModuleProperty bandwidthMax; private BandwidthHandler bandwidthHandler; @@ -50,41 +51,18 @@ public void SyncRemoteChannels() bandwidthHandler.Update(); } - /* - [CanBeNull] - public RemoteChannel GetRemoteChannel(long channelId) - { - //return bandwidthHandler.GetRemoteChannel(channelId); - } - */ - - /* - [CanBeNull] - public RemoteChannel GetRemoteChannelByUnit(Unit unit) - { - //return bandwidthHandler.GetRemoteChannelByUnit(unit); - } - */ - public bool HasFreeBandwidthFor(RemoteControlledUnit unit) { return bandwidthHandler.HasFreeBandwidthFor(unit); } - public void UseRemoteChannel(RemoteControlledTurret turret) + public void UseRemoteChannel(RemoteControlledCreature turret) { bandwidthHandler.UseRemoteChannel(turret); turret.RemoteChannelDeactivated += bandwidthHandler.OnRemoteChannelDeactivated; bandwidthHandler.Update(); } - /* - public void UseRemoteChannel(RemoteChannel newChannel) - { - bandwidthHandler.UseRemoteChannel(newChannel); - } - */ - public override void AcceptVisitor(IEntityVisitor visitor) { if (!TryAcceptVisitor(this, visitor)) @@ -107,45 +85,51 @@ protected override void OnAction() SyncRemoteChannels(); - Position? lockPosition; + var ammo = GetAmmo() as RemoteControlledUnit; - var myLock = GetLock(); + HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); - if (myLock is TerrainLock) - { - lockPosition = (myLock as TerrainLock).Location; - } - else if (myLock is UnitLock) - { - lockPosition = (myLock as UnitLock).Target.CurrentPosition; - } - else - { - OnError(ErrorCodes.InvalidLockType); + Position targetPosition; - return; - } + if (ammo.ED.Options.TurretType != TurretType.CombatDrone) + { - Position targetPosition = lockPosition.Value; - - Zone.Units - .OfType() + var 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), TurretDeployRange) .Any() .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); - - var r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); - if (r.hit) - { - OnError(ErrorCodes.LOSFailed); + var r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); - return; - } + if (r.hit) + { + OnError(ErrorCodes.LOSFailed); - var ammo = GetAmmo() as RemoteControlledUnit; - - HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); + return; + } + } + else + { + targetPosition = GetSpawnPosition(ParentRobot.CurrentPosition); + } var player = this.ParentRobot is Player robotAsPlayer ? robotAsPlayer @@ -157,16 +141,24 @@ protected override void OnAction() ammo.CheckEnablerExtensionsAndThrowIfFailed(player.Character, ErrorCodes.ExtensionLevelMismatchTerrain); } - RemoteControlledTurret fieldTurret = null; + RemoteControlledCreature remoteControlledCreature = null; if (ammo.ED.Options.TurretType == TurretType.Sentry) { - fieldTurret = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + remoteControlledCreature = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); + remoteControlledCreature.Behavior = Behavior.Create(BehaviorType.RemoteControlledTurret); } else if (ammo.ED.Options.TurretType == TurretType.Mining || ammo.ED.Options.TurretType == TurretType.Harvesting) { - fieldTurret = (IndustrialTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); - (fieldTurret as IndustrialTurret).SetTurretType(ammo.ED.Options.TurretType); + 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 { @@ -175,39 +167,47 @@ protected override void OnAction() if (player != null) { - fieldTurret.SetPlayer(player); + remoteControlledCreature.SetPlayer(player); } - fieldTurret.Owner = this.ParentRobot.Owner; - fieldTurret.Behavior = Behavior.Create(BehaviorType.RemoteControlled); - fieldTurret.SetBandwidthUsage(ammo.RemoteChannelBandwidthUsage); + remoteControlledCreature.Owner = this.ParentRobot.Owner; + remoteControlledCreature.SetBandwidthUsage(ammo.RemoteChannelBandwidthUsage); - UseRemoteChannel(fieldTurret); + UseRemoteChannel(remoteControlledCreature); var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); - fieldTurret.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); - fieldTurret.SetGroup(bandwidthHandler); + remoteControlledCreature.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); + remoteControlledCreature.SetGroup(bandwidthHandler); var finder = new ClosestWalkablePositionFinder(Zone, targetPosition); var position = finder.FindOrThrow(); - fieldTurret.HomePosition = position; - fieldTurret.HomeRange = 30; - fieldTurret.Orientation = FastRandom.NextInt(0, 3) * 0.25; - fieldTurret.CallForHelp = true; + remoteControlledCreature.HomePosition = position; + remoteControlledCreature.HomeRange = 50; + remoteControlledCreature.Orientation = FastRandom.NextInt(0, 3) * 0.25; + remoteControlledCreature.CallForHelp = true; var deployBeamBuilder = Beam.NewBuilder() .WithType(BeamType.dock_in) - .WithSource(fieldTurret.Player) - .WithTarget(fieldTurret) + .WithSource(remoteControlledCreature.Player) + .WithTarget(remoteControlledCreature) .WithState(BeamState.Hit) .WithDuration(TimeSpan.FromSeconds(5)); - fieldTurret.AddToZone(Zone, position, ZoneEnterType.Default, deployBeamBuilder); - Logger.Info($"[Remote Control] - spawned turret {fieldTurret.Eid} of type {ammo.ED.Options.TurretType} owned by {fieldTurret.Owner} represented by player {fieldTurret.Player} at {targetPosition}"); + remoteControlledCreature.AddToZone(Zone, position, ZoneEnterType.Default, deployBeamBuilder); + Logger.Info($"[Remote Control] - spawned turret {remoteControlledCreature.Eid} of type {ammo.ED.Options.TurretType} owned by {remoteControlledCreature.Owner} represented by player {remoteControlledCreature.Player} at {targetPosition}"); ConsumeAmmo(); } + + private Position GetSpawnPosition(Position spawnOrigin) + { + var spawnRangeMin = SpawnRangeMin; + var spawnRangeMax = SpawnRangeMax; + var spawnPosition = spawnOrigin.GetRandomPositionInRange2D(spawnRangeMin, spawnRangeMax).Clamp(Zone.Size); + + return spawnPosition; + } } } diff --git a/src/Perpetuum/Perpetuum.csproj b/src/Perpetuum/Perpetuum.csproj index c1451db41..8192ad755 100644 --- a/src/Perpetuum/Perpetuum.csproj +++ b/src/Perpetuum/Perpetuum.csproj @@ -677,8 +677,12 @@ + + + + @@ -688,9 +692,10 @@ + - + @@ -724,10 +729,11 @@ + - + diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index a60580474..4ca0be862 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -746,7 +746,7 @@ unit is IPBSObject || unit is WallHealer || unit is ProximityDeviceBase || (unit is BlobEmitterUnit b && b.IsPlayerSpawned) || - unit is RemoteControlledTurret; + unit is RemoteControlledCreature; } protected override void OnDead(Unit killer) diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index b85b9398b..92537b386 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -406,13 +406,6 @@ protected override bool IsDetected(Unit target) protected override void OnRemovedFromZone(IZone zone) { - var remoteController = _modules.Value.FirstOrDefault(x => x is RemoteControllerModule) ; - - if (remoteController != null) - { - (remoteController as RemoteControllerModule).CloseAllChannels(); - } - base.OnRemovedFromZone(zone); } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 9dc9c3186..d9cf16d75 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -408,6 +408,16 @@ public void RemoveFromZone(IBeamBuilder exitBeamBuilder = null) Debug.Assert(zone != null, "zone != null"); + if (this is Robot robot) + { + var remoteController = robot.Modules.FirstOrDefault(x => x is RemoteControllerModule); + + if (remoteController != null) + { + (remoteController as RemoteControllerModule).CloseAllChannels(); + } + } + if (exitBeamBuilder != null) zone.CreateBeam(exitBeamBuilder); @@ -934,6 +944,8 @@ public bool IsHostile(Unit unit) internal virtual bool IsHostile(Npc npc) { return false; } + internal virtual bool IsHostile(CombatDrone drone) { return false; } + public void StopMoving() { CurrentSpeed = 0; diff --git a/src/Perpetuum/Zones/Effects/GangEffect.cs b/src/Perpetuum/Zones/Effects/GangEffect.cs index 960c6753c..0ae6cd43c 100644 --- a/src/Perpetuum/Zones/Effects/GangEffect.cs +++ b/src/Perpetuum/Zones/Effects/GangEffect.cs @@ -18,7 +18,7 @@ protected override void OnTick() { var gang = Owner is Player player ? player.Gang - : Owner is RemoteControlledTurret turret + : Owner is RemoteControlledCreature turret ? turret.Player.Gang : null; diff --git a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs index c6042940c..8b80c3025 100644 --- a/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs +++ b/src/Perpetuum/Zones/Effects/ZoneEffects/ZoneEffectHandler.cs @@ -73,7 +73,7 @@ private IEnumerable GetEffects() private Func CanApplyEffect(ZoneEffect effect) { - return u => !u.EffectHandler.ContainsEffect(effect.Effect) && (!effect.PlayerOnly || u is Player || u is RemoteControlledTurret); + return u => !u.EffectHandler.ContainsEffect(effect.Effect) && (!effect.PlayerOnly || u is Player || u is RemoteControlledCreature); } public void OnEnterZone(Unit unit) diff --git a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs index 48e00e819..7ec663d26 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs @@ -226,28 +226,5 @@ private static List BuildPath(Node current) return stack.ToList(); } - - private 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/BaseAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs index 5933d856c..520161bd3 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BaseAI.cs @@ -1,6 +1,7 @@ using Perpetuum.Log; using Perpetuum.StateMachines; using Perpetuum.Zones.NpcSystem.AI.Behaviors; +using Perpetuum.Zones.NpcSystem.AI.CombatDrones; using System; using System.Diagnostics; @@ -43,10 +44,20 @@ protected virtual void ToAggressorAI() 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/Behavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs index 7ae89c890..cd5181937 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/Behavior.cs @@ -19,8 +19,10 @@ public static Behavior Create(BehaviorType type) return new AggressiveBehavior(); case BehaviorType.Passive: return new PassiveBehavior(); - case BehaviorType.RemoteControlled: - return new RemoteControlledBehavior(); + 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 index f1fdd4639..cf90931a3 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/BehaviorType.cs @@ -5,6 +5,7 @@ public enum BehaviorType Passive, Neutral, Aggressive, - RemoteControlled, + RemoteControlledTurret, + RemoteControlledDrone, } } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs b/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs deleted file mode 100644 index f838f6de0..000000000 --- a/src/Perpetuum/Zones/NpcSystem/AI/Behaviors/RemoteControlledBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Perpetuum.Zones.NpcSystem.AI.Behaviors -{ - public class RemoteControlledBehavior : Behavior - { - public RemoteControlledBehavior() : base(BehaviorType.RemoteControlled) - { - } - } -} 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 index 5a085d5c6..632757954 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -21,7 +21,8 @@ public BodyPullThreatHelper(SmartCreature smartCreature) public void Visit(Player player) { if (smartCreature.Behavior.Type != BehaviorType.Aggressive && - smartCreature.Behavior.Type != BehaviorType.RemoteControlled) + smartCreature.Behavior.Type != BehaviorType.RemoteControlledTurret && + smartCreature.Behavior.Type != BehaviorType.RemoteControlledDrone) { return; } @@ -82,7 +83,8 @@ public void Visit(AreaBomb bomb) public void Visit(Npc npc) { - if (smartCreature.Behavior.Type != BehaviorType.RemoteControlled) + if (smartCreature.Behavior.Type != BehaviorType.RemoteControlledTurret && + smartCreature.Behavior.Type != BehaviorType.RemoteControlledDrone) { return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index 53b6386f0..d38c48637 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -206,9 +206,7 @@ protected bool TryMakeFreeLockSlotFor(Hostile hostile) protected Hostile GetPrimaryOrMostHatedHostile() { - var primaryHostile = this.smartCreature.ThreatManager.Hostiles - .Where(h => h.Unit == (this.smartCreature.GetPrimaryLock() as UnitLock)?.Target) - .FirstOrDefault(); + var primaryHostile = GetPrimaryHostile(); if (primaryHostile != null) { @@ -218,6 +216,13 @@ protected Hostile GetPrimaryOrMostHatedHostile() return this.smartCreature.ThreatManager.GetMostHatedHostile(); } + protected Hostile GetPrimaryHostile() + { + return this.smartCreature.ThreatManager.Hostiles + .Where(h => h.Unit == (this.smartCreature.GetPrimaryLock() as UnitLock)?.Target) + .FirstOrDefault(); + } + private void SetLockForHostile(Hostile hostile) { var mostHated = GetPrimaryOrMostHatedHostile() == hostile; 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..0507aa3b0 --- /dev/null +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs @@ -0,0 +1,239 @@ +using Perpetuum.Collections; +using Perpetuum.PathFinders; +using Perpetuum.Units; +using Perpetuum.Zones.Movements; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Perpetuum.Zones.NpcSystem.AI.CombatDrones +{ + public class AttackCombatDroneAI : CombatAI + { + private Position lastTargetPosition; + private PathMovement movement; + private PathMovement nextMovement; + private CancellationTokenSource source; + private const int Sqrt2 = 141; + private const int Weight = 1000; + + 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.IsInHomeRange) + //{ + // smartCreature.AI.Push(new HomingAI(smartCreature)); + + // return; + //} + + if (!smartCreature.ThreatManager.IsThreatened) + { + ReturnToCommandBot(); + + return; + } + + this.UpdateHostile(time); + + base.Update(time); + } + + protected override void ToAggressorAI() { } + + private void ReturnToCommandBot() + { + smartCreature.AI.Pop(); + smartCreature.AI.Push(new EscortCombatDroneAI(smartCreature)); + this.WriteLog("Returning to command robot."); + } + + private void UpdateHostile(TimeSpan time) + { + var mostHated = GetPrimaryHostile(); + + if (mostHated == null) + { + return; + } + + if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) + { + 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) + { + //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 Task> FindNewAttackPositionAsync(Unit hostile) + { + this.source?.Cancel(); + this.source = new CancellationTokenSource(); + + return Task.Run(() => FindNewAttackPosition(hostile, this.source.Token), this.source.Token); + } + + private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) + { + var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestActionRange - 1).ToPoint(); + + smartCreature.StopMoving(); + + 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/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/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/Npc.cs b/src/Perpetuum/Zones/NpcSystem/Npc.cs index 44cd2435d..40ff31c43 100644 --- a/src/Perpetuum/Zones/NpcSystem/Npc.cs +++ b/src/Perpetuum/Zones/NpcSystem/Npc.cs @@ -171,6 +171,11 @@ internal override bool IsHostile(IndustrialTurret turret) return true; } + internal override bool IsHostile(CombatDrone drone) + { + return true; + } + protected override bool IsHostileFor(Unit unit) { return unit.IsHostile(this); @@ -178,7 +183,7 @@ protected override bool IsHostileFor(Unit unit) protected override void UpdateUnitVisibility(Unit target) { - if (target is SentryTurret) + if (target is RemoteControlledCreature) { UpdateVisibility(target); } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 545b3946c..cee518166 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -21,6 +21,7 @@ using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.Terrains.Materials.Plants; using Perpetuum.Players; +using Perpetuum.Zones.NpcSystem.AI.CombatDrones; namespace Perpetuum.Zones.NpcSystem { @@ -106,7 +107,7 @@ public SmartCreature() industrialValueManager = new IndustrialValueManager(); } - public void LookingForHostiles() + public virtual void LookingForHostiles() { foreach (var visibility in GetVisibleUnits()) { @@ -275,7 +276,7 @@ protected override void OnUnitTileChanged(Unit target) debounceBodyPull.Reset(); } - private void AddBodyPullThreat(Unit enemy) + protected void AddBodyPullThreat(Unit enemy) { if (!IsHostile(enemy)) { @@ -313,7 +314,7 @@ private int CalculateMaxCombatRange() public bool IsInAggroRange(Unit target) { - return IsStationary || IsInRangeOf3D(target, AggroRange); + return this is CombatDrone || IsStationary || IsInRangeOf3D(target, AggroRange); } public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) @@ -424,6 +425,10 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) AI.Push(new HarvestingIndustrialTurretAI(this)); } } + else if (this is CombatDrone) + { + AI.Push(new GuardCombatDroneAI(this)); + } else if (IsStationary) { AI.Push(new StationaryIdleAI(this)); diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs index 6a3403988..d6b6baf9b 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatPrimaryLockSelectionStrategy.cs @@ -7,5 +7,6 @@ public enum CombatPrimaryLockSelectionStrategy Closest, OptimalRange, HostileOrClosest, + PropagatedPrimary, } } diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs index 1b0184215..bca0f1365 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs @@ -1,4 +1,6 @@ using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.RemoteControl; +using System; using System.Collections.Generic; using System.Linq; @@ -16,6 +18,7 @@ public static class CombatStrategies { CombatPrimaryLockSelectionStrategy.Closest, TargetClosest }, { CombatPrimaryLockSelectionStrategy.OptimalRange, TargetWithinOptimal }, { CombatPrimaryLockSelectionStrategy.HostileOrClosest, TargetMostHatedOrClosest }, + { CombatPrimaryLockSelectionStrategy.PropagatedPrimary, TargetPropagatedPrimary }, }; public static CombatTargetSelectionStrategy GetStrategy(CombatPrimaryLockSelectionStrategy strategyType) @@ -35,6 +38,18 @@ public static bool TryInvokeStrategy(CombatPrimaryLockSelectionStrategy strategy return primaryLockSelectionStrategy(smartCreature, locks); } + private static bool TargetPropagatedPrimary(SmartCreature smartCreature, UnitLock[] locks) + { + var propagatedPrimary = locks.FirstOrDefault(x => x.Target == ((smartCreature as RemoteControlledCreature).Player.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; diff --git a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs index 25e06b1c5..07c5ca444 100644 --- a/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs +++ b/src/Perpetuum/Zones/NpcSystem/ThreatManagement/PseudoThreatManager.cs @@ -17,6 +17,11 @@ public PseudoThreatManager() _lock = new object(); } + public bool IsThreatened + { + get { return _pseudoThreats.Any(); } + } + public void AwardPseudoThreats(List alreadyAwarded, IZone zone, int ep) { var pseudoHostileUnits = new List(); diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index 546866635..fe8bd2106 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -47,7 +47,7 @@ public bool HasFreeBandwidthFor(RemoteControlledUnit unit) return BandwidthUsed <= owner.BandwidthMax - unit.RemoteChannelBandwidthUsage; } - public void UseRemoteChannel(RemoteControlledTurret turret) + public void UseRemoteChannel(RemoteControlledCreature turret) { UseRemoteChannel(new RemoteChannel(owner, turret)); } @@ -58,7 +58,7 @@ public void UseRemoteChannel(RemoteChannel newChannel) Interlocked.Exchange(ref dirty, 1); } - public void OnRemoteChannelDeactivated(RemoteControlledTurret turret) + public void OnRemoteChannelDeactivated(RemoteControlledCreature turret) { var channel = GetRemoteChannelByUnit(turret); diff --git a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs new file mode 100644 index 000000000..c28cde7cb --- /dev/null +++ b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs @@ -0,0 +1,121 @@ +using Perpetuum.Players; +using Perpetuum.Services.Standing; +using Perpetuum.Units; +using Perpetuum.Zones.Locking.Locks; +using Perpetuum.Zones.NpcSystem; +using System; +using System.Linq; + +namespace Perpetuum.Zones.RemoteControl +{ + public class CombatDrone : RemoteControlledCreature + { + private readonly IStandingHandler standingHandler; + private const double StandingLimit = 0.0; + + public double GuardRange { get; set; } + + public override bool IsStationary => false; + + public CombatDrone(IStandingHandler standingHandler) + { + this.standingHandler = standingHandler; + } + + public bool IsInGuardRange + { + get { return CurrentPosition.IsInRangeOf2D(HomePosition, GuardRange); } + } + + protected override bool IsHostileFor(Unit unit) + { + return unit.IsHostile(this); + } + + public override bool IsHostile(Player player) + { + if (Player != null && Player == player) + { + return false; + } + + if (Player.Gang != null && Player.Gang.IsMember(player.Character)) + { + return false; + } + + return IsHostilePlayer(player.Eid); + } + + public override void OnAggression(Unit victim) + { + this.Player.OnAggression(victim); + } + + /* + public override void LookingForHostiles() + { + foreach (var visibility in GetVisibleUnits().Where(x=>IsCommandBotPrimaryLock(x.Target)) + { + AddBodyPullThreat(visibility.Target); + } + } + */ + + protected override void OnUpdate(TimeSpan time) + { + HomePosition = Player.CurrentPosition; + base.OnUpdate(time); + } + + internal override bool IsHostile(Npc npc) + { + return true; + } + + protected override void OnUnitLockStateChanged(Lock @lock) + { + // Do nothing + } + + protected override void UpdateUnitVisibility(Unit target) + { + if (target is Npc) + { + UpdateVisibility(target); + } + } + + protected override bool IsDetected(Unit target) + { + if (!IsCommandBotPrimaryLock(target)) + { + return false; + } + + return base.IsDetected(target); + } + + private bool IsHostilePlayer(long playerEid) + { + var standing = standingHandler.GetStanding(Owner, playerEid); + + return StandingLimit >= standing; + } + + private bool IsCommandBotPrimaryLock(Unit unit) + { + var primaryLock = this.Player.GetPrimaryLock(); + + if (primaryLock != null && + primaryLock.State == Locking.LockState.Locked && + primaryLock is UnitLock && + (primaryLock as UnitLock).Target == unit) + { + return true; + } + + return false; + } + } +} diff --git a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs index 9f5c21c13..bbd2d5e7b 100644 --- a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -12,7 +12,7 @@ namespace Perpetuum.Zones.RemoteControl { - public class IndustrialTurret : RemoteControlledTurret + public class IndustrialTurret : RemoteControlledCreature { public TurretType TurretType { get; private set; } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs index 5281868ab..188f31da6 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteChannel.cs @@ -3,7 +3,7 @@ namespace Perpetuum.Zones.RemoteControl { - public delegate void RemoteChannelEventHandler(RemoteControlledTurret turret); + public delegate void RemoteChannelEventHandler(RemoteControlledCreature turret); public delegate void RemoteChannelEventHandler(RemoteChannel channel, T arg); public class RemoteChannel @@ -12,9 +12,9 @@ public class RemoteChannel public RemoteControllerModule Owner { get; private set; } - public RemoteControlledTurret Turret { get; set; } + public RemoteControlledCreature Turret { get; set; } - public RemoteChannel(RemoteControllerModule owner, RemoteControlledTurret turret) + public RemoteChannel(RemoteControllerModule owner, RemoteControlledCreature turret) { Id = FastRandom.NextLong(); Owner = owner; diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs similarity index 96% rename from src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs rename to src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 0efa12bce..5d1b64c45 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -10,7 +10,7 @@ namespace Perpetuum.Zones.RemoteControl { - public class RemoteControlledTurret : SmartCreature + public class RemoteControlledCreature : SmartCreature { private const double SentryTurretCallForHelpArmorThreshold = 0.8; private UnitDespawnHelper despawnHelper; diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index b262a5d26..7ce96d24b 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -6,7 +6,7 @@ namespace Perpetuum.Zones.RemoteControl { - public class SentryTurret : RemoteControlledTurret + public class SentryTurret : RemoteControlledCreature { private readonly IStandingHandler standingHandler; private const double StandingLimit = 0.0; diff --git a/src/Perpetuum/Zones/RemoteControl/TurretType.cs b/src/Perpetuum/Zones/RemoteControl/TurretType.cs index 64bd905cf..0e7cfd0a8 100644 --- a/src/Perpetuum/Zones/RemoteControl/TurretType.cs +++ b/src/Perpetuum/Zones/RemoteControl/TurretType.cs @@ -5,5 +5,6 @@ public enum TurretType Sentry, Mining, Harvesting, + CombatDrone, } } diff --git a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs index e14b5c193..eb75bd2f6 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs @@ -34,17 +34,17 @@ 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) + public static IEnumerable GetAlliedTurretsByPlayers(this IZone zone, IEnumerable players) { return !players.Any() - ? new RemoteControlledTurret[0] + ? new RemoteControlledCreature[0] : zone.Units - .Where(x => x is RemoteControlledTurret) + .Where(x => x is RemoteControlledCreature) .Join( players, - turret => ((RemoteControlledTurret)turret).Player, + turret => ((RemoteControlledCreature)turret).Player, player => player, - (turret, player) => (RemoteControlledTurret)turret); + (turret, player) => (RemoteControlledCreature)turret); } public static void SendPacketToGang(this IZone zone, Gang gang, IBuilder packetBuidler, long exceptMemberEid = 0L) From cc628dd6b3622ae33c4e84e101c0107c6da61688 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sat, 13 Jan 2024 20:30:51 +0500 Subject: [PATCH 433/457] - Industrial turrets now drop all the cargo each 5 min - Harvesting turrets no more harvesting empty tiles --- .../Modules/RemoteControlledDrillerModule.cs | 7 ++-- .../RemoteControlledHarvesterModule.cs | 9 ++--- src/Perpetuum/Robots/Robot.Locking.cs | 5 +++ src/Perpetuum/Zones/Locking/LockHandler.cs | 5 +++ .../Zones/NpcSystem/AI/IndustrialAI.cs | 37 ++++++++++--------- .../Zones/NpcSystem/SmartCreature.cs | 8 ++-- .../IndustrialStrategies.cs | 1 - .../Zones/RemoteControl/IndustrialTurret.cs | 34 ++++++----------- 8 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs index 88c26c7a0..263e18751 100644 --- a/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledDrillerModule.cs @@ -1,5 +1,6 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains.Materials; namespace Perpetuum.Modules @@ -21,11 +22,9 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override void OnAction() { - var zone = Zone; - - if (zone != null) + if (Zone != null) { - DoExtractMinerals(zone); + DoExtractMinerals(Zone); } } } diff --git a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs index 9f2ad2af5..fb72ff92a 100644 --- a/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs +++ b/src/Perpetuum/Modules/RemoteControlledHarvesterModule.cs @@ -1,5 +1,6 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; +using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; namespace Perpetuum.Modules @@ -20,14 +21,10 @@ public override void AcceptVisitor(IEntityVisitor visitor) protected override void OnAction() { - var zone = Zone; - - if (zone == null) + if (Zone != null) { - return; + DoHarvesting(Zone); } - - DoHarvesting(zone); } } } diff --git a/src/Perpetuum/Robots/Robot.Locking.cs b/src/Perpetuum/Robots/Robot.Locking.cs index 2f308a826..662bf7632 100644 --- a/src/Perpetuum/Robots/Robot.Locking.cs +++ b/src/Perpetuum/Robots/Robot.Locking.cs @@ -46,6 +46,11 @@ public Lock GetPrimaryLock() return _lockHandler.GetPrimaryLock(); } + public IEnumerable GetSecondaryLocks() + { + return _lockHandler.GetSecondaryLocks(); + } + public bool IsInLockingRange(Unit unit) { return _lockHandler.IsInLockingRange(unit); diff --git a/src/Perpetuum/Zones/Locking/LockHandler.cs b/src/Perpetuum/Zones/Locking/LockHandler.cs index 001c975f3..c73fe2d71 100644 --- a/src/Perpetuum/Zones/Locking/LockHandler.cs +++ b/src/Perpetuum/Zones/Locking/LockHandler.cs @@ -181,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); diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs index 499a04405..680428833 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -8,16 +8,21 @@ 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) @@ -32,6 +37,7 @@ public override void Enter() .Select(m => new ModuleActivator(m)) .ToList(); processIndustrialTargetsTimer.Update(industrialTargetsUpdateFrequency); + processEjectTimer.Update(ejectCargoFrequency); primarySelectTimer.Update(industrialTargetsUpdateFrequency); base.Enter(); @@ -42,6 +48,7 @@ public override void Update(TimeSpan time) UpdateIndustrialTargets(time); UpdatePrimaryTarget(time); RunModules(time); + EjectCargo(time); } protected virtual IndustrialPrimaryLockSelectionStrategySelector InitSelector() @@ -82,6 +89,17 @@ protected void RunModules(TimeSpan time) } } + protected void EjectCargo(TimeSpan time) + { + processEjectTimer.Update(time); + + if (processEjectTimer.Passed) + { + processEjectTimer.Reset(); + (smartCreature as IndustrialTurret).EjectCargo(); + } + } + protected virtual TimeSpan SetPrimaryDwellTime() { return FastRandom.NextTimeSpan(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10)); @@ -183,23 +201,8 @@ protected bool TryMakeFreeLockSlotFor(IndustrialTarget industrialTarget) return true; } - this.smartCreature.IndustrialValueManager.IndustrialTargets - .Where(x => x.IndustrialValue == 0) - .ForEach(x => this.smartCreature.GetLockByPosition(x.Position).Cancel()); - - var weakestLock = this.smartCreature.IndustrialValueManager.IndustrialTargets - .SkipWhile(h => h != industrialTarget) - .Skip(1) - .Select(h => this.smartCreature.GetLockByPosition(h.Position)) - .LastOrDefault(); - - if (weakestLock == null) - { - return false; - } - - weakestLock.Cancel(); - + this.smartCreature.GetSecondaryLocks().ForEach(x => x.Cancel()); + return true; } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index cee518166..45f85a1e9 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -165,11 +165,11 @@ public void LookingForHarvestingTargets() foreach (PlantType plantType in availablePlantTypes) { - var plantsCounter = Zone.CountPlantsInArea(plantType, area); + var plants = Zone.GetPlantPositionsInArea(plantType, area); - if (plantsCounter > 0) + if (plants.Any()) { - var valuablePositions = Zone.GetPlantPositionsInArea(plantType, area) + var valuablePositions = plants .Select(x => Zone.FixZ(x)) .Where(x => x.IsInRangeOf2D(this.CurrentPosition, this.BestActionRange)); @@ -179,7 +179,7 @@ public void LookingForHarvestingTargets() if (plant.material > 0) { - IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Mineral, plant.material); + IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Plant, plant.material); } } } diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs index e421cb6d0..dfb4714b6 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/IndustrialStrategies.cs @@ -42,7 +42,6 @@ private static bool TargetRichestTile(SmartCreature smartCreature, TerrainLock[] var industrialTargetLocks = locks.Where(u => industrialTargets.Any(h => h.Position.ToString() == u.Location.ToString())); var mostHostileLock = industrialTargetLocks - //.Where(k => k.Location.IsInRangeOf2D(smartCreature.CurrentPosition, smartCreature.BestCombatRange)) .OrderByDescending(u => industrialTargets.FirstOrDefault(h => h.Position.ToString() == u.Location.ToString())?.IndustrialValue ?? 0) .FirstOrDefault(); diff --git a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs index bbd2d5e7b..c492c242e 100644 --- a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Linq; using System; +using System.Security.Policy; namespace Perpetuum.Zones.RemoteControl { @@ -41,6 +42,13 @@ internal override bool IsHostile(Npc npc) } protected override void OnRemovedFromZone(IZone zone) + { + EjectCargo(); + + base.OnRemovedFromZone(zone); + } + + public void EjectCargo() { using (var scope = Db.CreateTransaction()) { @@ -54,20 +62,6 @@ protected override void OnRemovedFromZone(IZone zone) var lootItems = new List(); - foreach (var item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) - { - var wrapper = item as VolumeWrapperContainer; - - if (wrapper == null) - { - continue; - } - - lootItems.AddRange(wrapper.GetLootItems()); - wrapper.SetAllowDelete(); - Repository.Delete(wrapper); - } - foreach (var item in robotInventory .GetItems() .Where(i => !i.ED.AttributeFlags.NonStackable)) @@ -81,18 +75,16 @@ protected override void OnRemovedFromZone(IZone zone) .SetRepackaged(item.ED.AttributeFlags.Repackable) .Build()); } - else - { - robotInventory.RemoveItemOrThrow(item); - Repository.Delete(item); - } + + robotInventory.RemoveItemOrThrow(item); + Repository.Delete(item); } if (lootItems.Count > 0) { var lootContainer = LootContainer.Create() .AddLoot(lootItems) - .BuildAndAddToZone(zone, CurrentPosition); + .BuildAndAddToZone(Zone, CurrentPosition); } this.Save(); @@ -104,8 +96,6 @@ protected override void OnRemovedFromZone(IZone zone) Logger.Exception(ex); } } - - base.OnRemovedFromZone(zone); } } } From 3154720bf2ed0fad9447ba995b26b7e03c6824a5 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Mon, 15 Jan 2024 06:16:17 +0500 Subject: [PATCH 434/457] - Turrets and drones now can fight each other --- src/Perpetuum/Units/UnitExtensions.cs | 5 + .../Zones/NpcSystem/AI/AggressorAI.cs | 195 +---------------- .../NpcSystem/AI/BodyPullThreatHelper.cs | 64 +++++- src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 195 ++++++++++++++++- .../AI/CombatDrones/AttackCombatDroneAI.cs | 200 +----------------- .../Zones/RemoteControl/CombatDrone.cs | 77 +++---- .../Zones/RemoteControl/IndustrialTurret.cs | 4 +- .../RemoteControl/RemoteControlledCreature.cs | 43 +++- .../Zones/RemoteControl/SentryTurret.cs | 49 +++-- 9 files changed, 366 insertions(+), 466 deletions(-) diff --git a/src/Perpetuum/Units/UnitExtensions.cs b/src/Perpetuum/Units/UnitExtensions.cs index 74c5a6cba..260e36c4a 100644 --- a/src/Perpetuum/Units/UnitExtensions.cs +++ b/src/Perpetuum/Units/UnitExtensions.cs @@ -192,6 +192,11 @@ public static void SpreadAssistThreatToNpcs(this Unit unit, Unit assistant, Thre public static void SendPacketToWitnessPlayers(this Unit source, Packet packet, bool sendSelf = false) { + if (source == null) + { + return; + } + if (sendSelf) { (source as Player)?.Session.SendPacket(packet); diff --git a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs index 7ec663d26..13811aa2a 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/AggressorAI.cs @@ -1,25 +1,9 @@ -using Perpetuum.Collections; -using Perpetuum.PathFinders; -using Perpetuum.Units; -using Perpetuum.Zones.Movements; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System; namespace Perpetuum.Zones.NpcSystem.AI { public class AggressorAI : CombatAI { - private Position lastTargetPosition; - private PathMovement movement; - private PathMovement nextMovement; - private CancellationTokenSource source; - private const int Sqrt2 = 141; - private const int Weight = 1000; - public AggressorAI(SmartCreature smartCreature) : base(smartCreature) { } public override void Exit() @@ -40,7 +24,7 @@ public override void Update(TimeSpan time) if (!smartCreature.ThreatManager.IsThreatened) { - EnterEvadeMode(); + ReturnToHomePosition(); return; } @@ -51,180 +35,5 @@ public override void Update(TimeSpan time) } protected override void ToAggressorAI() { } - - private void EnterEvadeMode() - { - smartCreature.AI.Pop(); - smartCreature.AI.Push(new HomingAI(smartCreature)); - this.WriteLog("Enter evade mode."); - } - - private void UpdateHostile(TimeSpan time) - { - var mostHated = GetPrimaryOrMostHatedHostile(); - - if (mostHated == null) - { - return; - } - - if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) - { - 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) - { - 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 Task> FindNewAttackPositionAsync(Unit hostile) - { - this.source?.Cancel(); - this.source = new CancellationTokenSource(); - - return Task.Run(() => FindNewAttackPosition(hostile, this.source.Token), this.source.Token); - } - - private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) - { - var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestActionRange - 1).ToPoint(); - - smartCreature.StopMoving(); - - 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(); - - closed.Add(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/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index 632757954..f4c5237e8 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -5,11 +5,17 @@ using Perpetuum.Zones.Eggs; using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.NpcSystem.ThreatManaging; +using Perpetuum.Zones.RemoteControl; using System.Linq; namespace Perpetuum.Zones.NpcSystem.AI { - public class BodyPullThreatHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor + public class BodyPullThreatHelper : + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor, + IEntityVisitor { private readonly SmartCreature smartCreature; @@ -108,5 +114,61 @@ public void Visit(Npc npc) smartCreature.AddThreat(npc, new Threat(ThreatType.Bodypull, threat)); } + + public void Visit(SentryTurret sentryTurret) + { + 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 == sentryTurret.Eid)) + { + return; + } + + if (!smartCreature.IsInAggroRange(sentryTurret)) + { + return; + } + + var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + + smartCreature.AddThreat(sentryTurret, new Threat(ThreatType.Bodypull, threat)); + } + + public void Visit(CombatDrone combatDrone) + { + 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 == combatDrone.Eid)) + { + return; + } + + if (!smartCreature.IsInAggroRange(combatDrone)) + { + return; + } + + var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + + smartCreature.AddThreat(combatDrone, new Threat(ThreatType.Bodypull, threat)); + } } } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index d38c48637..4b5b9eb07 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -1,25 +1,39 @@ -using Perpetuum.Modules.Weapons; +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; 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; @@ -223,6 +237,91 @@ protected Hostile GetPrimaryHostile() .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; + } + + if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) + { + 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; @@ -288,5 +387,99 @@ private bool SelectPrimaryTarget() 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(); + + 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 index 0507aa3b0..0541705db 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatDrones/AttackCombatDroneAI.cs @@ -1,26 +1,10 @@ -using Perpetuum.Collections; -using Perpetuum.PathFinders; -using Perpetuum.Units; -using Perpetuum.Zones.Movements; -using Perpetuum.Zones.NpcSystem.TargettingStrategies; +using Perpetuum.Zones.NpcSystem.TargettingStrategies; using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace Perpetuum.Zones.NpcSystem.AI.CombatDrones { public class AttackCombatDroneAI : CombatAI { - private Position lastTargetPosition; - private PathMovement movement; - private PathMovement nextMovement; - private CancellationTokenSource source; - private const int Sqrt2 = 141; - private const int Weight = 1000; - public AttackCombatDroneAI(SmartCreature smartCreature) : base(smartCreature) { } protected override CombatPrimaryLockSelectionStrategySelector InitSelector() @@ -39,201 +23,25 @@ public override void Exit() public override void Update(TimeSpan time) { - //if (!smartCreature.IsInHomeRange) - //{ - // smartCreature.AI.Push(new HomingAI(smartCreature)); - - // return; - //} - if (!smartCreature.ThreatManager.IsThreatened) { - ReturnToCommandBot(); + ReturnToHomePosition(); return; } - this.UpdateHostile(time); + this.UpdateHostile(time, false); base.Update(time); } protected override void ToAggressorAI() { } - private void ReturnToCommandBot() + protected override void ReturnToHomePosition() { smartCreature.AI.Pop(); smartCreature.AI.Push(new EscortCombatDroneAI(smartCreature)); this.WriteLog("Returning to command robot."); } - - private void UpdateHostile(TimeSpan time) - { - var mostHated = GetPrimaryHostile(); - - if (mostHated == null) - { - return; - } - - if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) - { - 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) - { - //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 Task> FindNewAttackPositionAsync(Unit hostile) - { - this.source?.Cancel(); - this.source = new CancellationTokenSource(); - - return Task.Run(() => FindNewAttackPosition(hostile, this.source.Token), this.source.Token); - } - - private List FindNewAttackPosition(Unit hostile, CancellationToken cancellationToken) - { - var end = hostile.CurrentPosition.GetRandomPositionInRange2D(0, smartCreature.BestActionRange - 1).ToPoint(); - - smartCreature.StopMoving(); - - 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/RemoteControl/CombatDrone.cs b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs index c28cde7cb..75d806df6 100644 --- a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs +++ b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs @@ -1,25 +1,22 @@ -using Perpetuum.Players; +using Perpetuum.EntityFramework; +using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; using System; -using System.Linq; namespace Perpetuum.Zones.RemoteControl { public class CombatDrone : RemoteControlledCreature { - private readonly IStandingHandler standingHandler; - private const double StandingLimit = 0.0; - public double GuardRange { get; set; } public override bool IsStationary => false; public CombatDrone(IStandingHandler standingHandler) + : base(standingHandler) { - this.standingHandler = standingHandler; } public bool IsInGuardRange @@ -27,24 +24,17 @@ public bool IsInGuardRange get { return CurrentPosition.IsInRangeOf2D(HomePosition, GuardRange); } } - protected override bool IsHostileFor(Unit unit) + public override void AcceptVisitor(IEntityVisitor visitor) { - return unit.IsHostile(this); + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } public override bool IsHostile(Player player) { - if (Player != null && Player == player) - { - return false; - } - - if (Player.Gang != null && Player.Gang.IsMember(player.Character)) - { - return false; - } - - return IsHostilePlayer(player.Eid); + return IsHostilePlayer(player); } public override void OnAggression(Unit victim) @@ -52,15 +42,10 @@ public override void OnAggression(Unit victim) this.Player.OnAggression(victim); } - /* - public override void LookingForHostiles() + protected override bool IsHostileFor(Unit unit) { - foreach (var visibility in GetVisibleUnits().Where(x=>IsCommandBotPrimaryLock(x.Target)) - { - AddBodyPullThreat(visibility.Target); - } + return unit.IsHostile(this); } - */ protected override void OnUpdate(TimeSpan time) { @@ -68,39 +53,43 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); } + protected override bool IsDetected(Unit target) + { + if (!IsCommandBotPrimaryLock(target)) + { + return false; + } + + return base.IsDetected(target); + } + internal override bool IsHostile(Npc npc) { return true; } - protected override void OnUnitLockStateChanged(Lock @lock) + internal override bool IsHostile(CombatDrone drone) { - // Do nothing + return IsHostilePlayer(drone.Player); } - protected override void UpdateUnitVisibility(Unit target) + internal override bool IsHostile(SentryTurret turret) { - if (target is Npc) - { - UpdateVisibility(target); - } + return IsHostilePlayer(turret.Player); } - protected override bool IsDetected(Unit target) + protected override void OnUnitLockStateChanged(Lock @lock) { - if (!IsCommandBotPrimaryLock(target)) - { - return false; - } - - return base.IsDetected(target); + // Do nothing } - private bool IsHostilePlayer(long playerEid) + protected override void UpdateUnitVisibility(Unit target) { - var standing = standingHandler.GetStanding(Owner, playerEid); - - return StandingLimit >= standing; + if (target is Npc || + target is RemoteControlledCreature) + { + UpdateVisibility(target); + } } private bool IsCommandBotPrimaryLock(Unit unit) diff --git a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs index c492c242e..8a64ba831 100644 --- a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -10,6 +10,7 @@ using System.Linq; using System; using System.Security.Policy; +using Perpetuum.Services.Standing; namespace Perpetuum.Zones.RemoteControl { @@ -17,7 +18,8 @@ public class IndustrialTurret : RemoteControlledCreature { public TurretType TurretType { get; private set; } - public IndustrialTurret() + public IndustrialTurret(IStandingHandler standingHandler) + : base(standingHandler) { } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 5d1b64c45..572082183 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -1,11 +1,8 @@ -using Perpetuum.Data; -using Perpetuum.ExportedTypes; -using Perpetuum.Items; +using Perpetuum.Items; using Perpetuum.Players; +using Perpetuum.Services.Standing; using Perpetuum.Units; -using Perpetuum.Zones.Beams; using Perpetuum.Zones.NpcSystem; -using Perpetuum.Zones.NpcSystem.AI.Behaviors; using System; namespace Perpetuum.Zones.RemoteControl @@ -15,6 +12,8 @@ public class RemoteControlledCreature : SmartCreature private const double SentryTurretCallForHelpArmorThreshold = 0.8; private UnitDespawnHelper despawnHelper; private ItemProperty remoteChannelBandwidthUsage = ItemProperty.None; + private readonly IStandingHandler standingHandler; + private const double StandingLimit = 0.0; public event RemoteChannelEventHandler RemoteChannelDeactivated; @@ -34,6 +33,11 @@ public TimeSpan DespawnTime public override double CallForHelpArmorThreshold => SentryTurretCallForHelpArmorThreshold; + public RemoteControlledCreature(IStandingHandler standingHandler) + { + this.standingHandler = standingHandler; + } + public void SetPlayer(Player player) { Player = player; @@ -60,5 +64,34 @@ protected override void OnDead(Unit killer) { base.OnDead(killer); } + + protected bool IsHostilePlayer(Player targetPlayer) + { + if (Player != null && Player == targetPlayer) + { + return false; + } + + if (Player.Gang != null && Player.Gang.IsMember(targetPlayer.Character)) + { + return false; + } + + var corporationStanding = standingHandler.GetStanding(Player.CorporationEid, targetPlayer.CorporationEid); + + if (corporationStanding > StandingLimit) + { + return false; + } + + var personalStanding = standingHandler.GetStanding(Owner, targetPlayer.Character.Eid); + + if (personalStanding > StandingLimit) + { + return false; + } + + return true; + } } } diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 7ce96d24b..3592c2b1d 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -1,4 +1,4 @@ -using Perpetuum.Groups.Corporations; +using Perpetuum.EntityFramework; using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; @@ -8,12 +8,17 @@ namespace Perpetuum.Zones.RemoteControl { public class SentryTurret : RemoteControlledCreature { - private readonly IStandingHandler standingHandler; - private const double StandingLimit = 0.0; - public SentryTurret(IStandingHandler standingHandler) + : base(standingHandler) + { + } + + public override void AcceptVisitor(IEntityVisitor visitor) { - this.standingHandler = standingHandler; + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } } protected override bool IsHostileFor(Unit unit) @@ -21,19 +26,9 @@ protected override bool IsHostileFor(Unit unit) return unit.IsHostile(this); } - public override bool IsHostile(Player player) + public override bool IsHostile(Player targetPlayer) { - if (Player != null && Player == player) - { - return false; - } - - if (Player.Gang != null && Player.Gang.IsMember(player.Character)) - { - return false; - } - - return IsHostilePlayer(player.Eid); + return IsHostilePlayer(targetPlayer); } public override void OnAggression(Unit victim) @@ -46,19 +41,23 @@ internal override bool IsHostile(Npc npc) return true; } - protected override void UpdateUnitVisibility(Unit target) + internal override bool IsHostile(CombatDrone drone) { - if (target is Npc) - { - UpdateVisibility(target); - } + return IsHostilePlayer(drone.Player); } - private bool IsHostilePlayer(long playerEid) + internal override bool IsHostile(SentryTurret turret) { - var standing = standingHandler.GetStanding(Owner, playerEid); + return IsHostilePlayer(turret.Player); + } - return StandingLimit >= standing; + protected override void UpdateUnitVisibility(Unit target) + { + if (target is Npc || + target is RemoteControlledCreature) + { + UpdateVisibility(target); + } } } } \ No newline at end of file From 89a198dcbede0f396831f0dbeca26115588ad6a7 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Tue, 16 Jan 2024 04:32:34 +0500 Subject: [PATCH 435/457] - RCU are now explode on playerr left zone - Alpha is now safe until both players flagged --- src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs | 2 +- .../Zones/RemoteControl/RemoteControlledCreature.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs index fe8bd2106..7a05b8711 100644 --- a/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs +++ b/src/Perpetuum/Zones/RemoteControl/BandwidthHandler.cs @@ -75,7 +75,7 @@ public void CloseAllChannels() { foreach (var channel in channels) { - channel.Turret.RemoveFromZone(); + channel.Turret.Kill(); ReleaseRemoteChannel(channel); } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 572082183..3bae0b560 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -67,6 +67,11 @@ protected override void OnDead(Unit killer) protected bool IsHostilePlayer(Player targetPlayer) { + if (Zone.Configuration.IsAlpha && !Player.HasPvpEffect && !targetPlayer.HasPvpEffect) + { + return false; + } + if (Player != null && Player == targetPlayer) { return false; From eefd097d9041c05cd45a29bffaed1d8543f80cd2 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Wed, 17 Jan 2024 20:59:58 +0500 Subject: [PATCH 436/457] - Fixed issue with account stuck while safelogging with active industrial turrets - Fixed issue with industrial turrets don't dsrop loot on disappear --- src/Perpetuum/Players/Player.cs | 10 ++++++++++ src/Perpetuum/Units/Unit.cs | 19 ++++++++----------- .../Zones/NpcSystem/AI/IndustrialAI.cs | 2 +- .../Zones/RemoteControl/IndustrialTurret.cs | 13 +++++-------- .../RemoteControl/RemoteControlledCreature.cs | 3 +-- src/Perpetuum/Zones/ZoneExtensions.cs | 4 +++- 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 4ca0be862..9e1f96080 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -939,6 +939,16 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) p.Clear(); } + protected override void OnBeforeRemovedFromZone(IZone zone) + { + var remoteController = this.Modules?.FirstOrDefault(x => x is RemoteControllerModule); + + if (remoteController != null) + { + (remoteController as RemoteControllerModule).CloseAllChannels(); + } + } + protected override void OnRemovedFromZone(IZone zone) { Session.SendPacket(ExitPacketBuilder); diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index d9cf16d75..70f3deaa4 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -403,26 +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) + { return; + } Debug.Assert(zone != null, "zone != null"); - if (this is Robot robot) - { - var remoteController = robot.Modules.FirstOrDefault(x => x is RemoteControllerModule); - - if (remoteController != null) - { - (remoteController as RemoteControllerModule).CloseAllChannels(); - } - } - if (exitBeamBuilder != null) + { zone.CreateBeam(exitBeamBuilder); + } + OnBeforeRemovedFromZone(zone); zone.RemoveUnit(this); - OnRemovedFromZone(zone); zone.UpdateUnitRelations(this); RemovedFromZone?.Invoke(this); @@ -430,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); diff --git a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs index 680428833..a77586071 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/IndustrialAI.cs @@ -96,7 +96,7 @@ protected void EjectCargo(TimeSpan time) if (processEjectTimer.Passed) { processEjectTimer.Reset(); - (smartCreature as IndustrialTurret).EjectCargo(); + (smartCreature as IndustrialTurret).EjectCargo(smartCreature.Zone); } } diff --git a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs index 8a64ba831..990c9a665 100644 --- a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -43,19 +43,16 @@ internal override bool IsHostile(Npc npc) return false; } - protected override void OnRemovedFromZone(IZone zone) + protected override void OnBeforeRemovedFromZone(IZone zone) { - EjectCargo(); - - base.OnRemovedFromZone(zone); + EjectCargo(zone); + base.OnBeforeRemovedFromZone(zone); } - public void EjectCargo() + public void EjectCargo(IZone zone) { using (var scope = Db.CreateTransaction()) { - EnlistTransaction(); - try { var robotInventory = GetContainer(); @@ -86,7 +83,7 @@ public void EjectCargo() { var lootContainer = LootContainer.Create() .AddLoot(lootItems) - .BuildAndAddToZone(Zone, CurrentPosition); + .BuildAndAddToZone(zone, CurrentPosition); } this.Save(); diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 3bae0b560..26d5499b8 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -54,10 +54,9 @@ protected override void OnUpdate(TimeSpan time) despawnHelper?.Update(time, this); } - protected override void OnRemovedFromZone(IZone zone) + protected override void OnBeforeRemovedFromZone(IZone zone) { RemoteChannelDeactivated(this); - base.OnRemovedFromZone(zone); } protected override void OnDead(Unit killer) diff --git a/src/Perpetuum/Zones/ZoneExtensions.cs b/src/Perpetuum/Zones/ZoneExtensions.cs index 0deb7077e..6f4375d21 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.cs @@ -213,7 +213,9 @@ public static void DoAoeDamageAsync(this IZone zone,IBuilder damageB public static void DoAoeDamage(this IZone zone,IBuilder damageBuilder) { var damageInfo = damageBuilder.Build(); - var units = zone.Units.WithinRange(damageInfo.sourcePosition, damageInfo.Range); + var units = zone.Units + .WithinRange(damageInfo.sourcePosition, damageInfo.Range); + //.Where(x=>x.HasTeleportSicknessEffect); foreach (var unit in units) { From 33b11933247962c30f80e333f3d99b458f1ec78e Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sat, 27 Jan 2024 18:16:36 -0500 Subject: [PATCH 437/457] OP-480 Update readme with initial instructions for workspace setup --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/README.md b/README.md index 8c7d74cf4..f5715d7c2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,62 @@ To see what we are working on acquaint yourself with our: To contribute, join the team and we will get you up and running, or, show us your moxie and submit a pull request! +# How to setup for development? +You need to clone this repository and 2 others (database, asset). + +The PerpetuumServer handle server communication with the client. + +The OPResource is an asset host that the client access when connecting to a server, the address can be configured in the data folder in the ini file. + +The OPDB is contains the database schema definition and the migration patches for each release. + +## Getting the source and the necessary files +1. Clone this repository (github page: [here](https://github.com/OpenPerpetuum/PerpetuumServer)) (folder/PerpetuumDedicated) +2. Clone the OPDB repository (github page: [here](https://github.com/OpenPerpetuum/OPDB)) (folder/OPDB) +3. Clone the asset respo (github page: [here](https://github.com/OpenPerpetuum/OPResource)) (folder/OPResource) +4. Get official Perpetuum Dedicated server installer, depot ID (with steamcmd): **693061** + - (steam) Library -> Home -> Above search select *Tools*, search for *PerpetuumDedicated*, install it +5. Get layers archive and icons, available in a public google drive folder [here](https://drive.google.com/drive/folders/1V6Dnytm0eZCuA_2PZHeUGslNzPrmXKtD) +Note that the Gamma_layers is compatible with release P26+ + +Here save the layers rar file + icons zip file. +6. (Requires Windows for now) Run the PerpetuumDedicated from steam or directly the exe, install SQL server express (known issues with Win11+, unable to install, use windows 10 to install), initialize the DB when asked at the end. + +At this point, take note of the path of the installed server, usually `C:\PerpetuumDedicated`. + +## Move/extraction of files +1. Extract layers archive into the data folder of the PerpetuumDedicated installation +2. Extract icons from the archive to the `OPResource/lang0000` folder + +## Initialize/Reset DB (windows) +Here you can run the following steps to initialize for the first time or reset the DB to a valid state for the given release version you have checked out in the OPDB repository. + +1. Run the file `OPDB/apply_all.bat` to reset the DB (wipe data and restore valid version) +The script is running a background script for each release we have. + +## Run the server +You need to have asset hosting, database hosting, server hosting. + +This section is an example to do the setup locally on windows 10 but also work on windows 11. + +1. Asset host, run node server on the `index.js` from the `OPResource` repository. +2. Database host, should already be setup with windows and SQL Server Express. +3. Server host (debug): Using visual studio running the PerpetuumServer project. + +## Client setup +1. Open the game +2. Click on `Server list` +3. Click on `Add private server` +4. Enter the name ex: `local` +5. Enter the server address ex: `127.0.0.1` +6. Click on connect +7. Default credential for local developement is defined in the `OPDB/apply_all.bat` (test/test) +This account have admin access of the local server + +## In-game admin commands +The admins commands are defined in this file: `src/Perpetuum/Commands.cs` + + Note: This is a divergent fork and changes within may not be generally applicable outside of the Open Perpetuum Project. From 804ed24e4044eebb94b0abf05ab5a18db0d7af34 Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sun, 28 Jan 2024 11:40:14 -0500 Subject: [PATCH 438/457] Fix readme formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f5715d7c2..00b19a0bc 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The OPDB is contains the database schema definition and the migration patches fo Note that the Gamma_layers is compatible with release P26+ Here save the layers rar file + icons zip file. + 6. (Requires Windows for now) Run the PerpetuumDedicated from steam or directly the exe, install SQL server express (known issues with Win11+, unable to install, use windows 10 to install), initialize the DB when asked at the end. At this point, take note of the path of the installed server, usually `C:\PerpetuumDedicated`. From 8de60ff78ac4f732cd30efd47fc4d4a3c757643e Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sun, 28 Jan 2024 11:50:10 -0500 Subject: [PATCH 439/457] Fix readme formatting --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00b19a0bc..d9e9082ec 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,9 @@ At this point, take note of the path of the installed server, usually `C:\Perpet ## Move/extraction of files 1. Extract layers archive into the data folder of the PerpetuumDedicated installation -2. Extract icons from the archive to the `OPResource/lang0000` folder +2. Extract icons from the archive to the `OPResource/lang0000` folder. + +Note that the icons archive does not contain icons for new items, the resource server will substitute the default icon instead. The default icon is the blue cube. ## Initialize/Reset DB (windows) Here you can run the following steps to initialize for the first time or reset the DB to a valid state for the given release version you have checked out in the OPDB repository. From 90bd5c454c77f536a881a28588f85fac09848840 Mon Sep 17 00:00:00 2001 From: Philippe Cloutier Date: Sun, 28 Jan 2024 12:54:14 -0500 Subject: [PATCH 440/457] Update default icon details --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9e9082ec..727a73102 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ At this point, take note of the path of the installed server, usually `C:\Perpet 1. Extract layers archive into the data folder of the PerpetuumDedicated installation 2. Extract icons from the archive to the `OPResource/lang0000` folder. -Note that the icons archive does not contain icons for new items, the resource server will substitute the default icon instead. The default icon is the blue cube. +Note that the icons archive does not contain icons for new items, the resource server will substitute with the [default icon](https://github.com/OpenPerpetuum/OPResource/blob/dev/lang0000/icons/default/00000000.png) instead. ## Initialize/Reset DB (windows) Here you can run the following steps to initialize for the first time or reset the DB to a valid state for the given release version you have checked out in the OPDB repository. From 21ca7732d64366d4364d9847e1802663e42175f1 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Tue, 6 Feb 2024 11:31:25 +0500 Subject: [PATCH 441/457] - RCU are now getting more aggro from npcs - Turrets don't hate their owner anymore - Turrets won't shoot flagged enemy on Alphas if their owner isn't flagged - Minor code cleanup performed --- src/Perpetuum.ExportedTypes/AggregateField.cs | 820 +++++++++--------- src/Perpetuum/Modules/ActiveModule.cs | 117 +-- .../Modules/RemoteControllerModule.cs | 53 +- src/Perpetuum/Modules/Weapons/WeaponModule.cs | 66 +- .../NpcSystem/AI/BodyPullThreatHelper.cs | 22 +- .../Zones/NpcSystem/SmartCreature.cs | 158 ++-- .../RemoteControl/RemoteControlledCreature.cs | 41 +- .../Scanning/Modules/ItemScannerModule.cs | 59 +- src/Perpetuum/Zones/ZoneExtensions.cs | 147 ++-- 9 files changed, 741 insertions(+), 742 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/AggregateField.cs b/src/Perpetuum.ExportedTypes/AggregateField.cs index f53a12281..1797a52a1 100644 --- a/src/Perpetuum.ExportedTypes/AggregateField.cs +++ b/src/Perpetuum.ExportedTypes/AggregateField.cs @@ -5,438 +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, + 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, + 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, + 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, + 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_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, + 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, + 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, + 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, + 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, + 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_damage_modifier = 675, - remote_control_damage_modifier_modifier = 676, + 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/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 0bab55f56..24f83031a 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; using Perpetuum.Containers; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -15,6 +12,9 @@ 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 { @@ -31,7 +31,7 @@ public abstract partial class ActiveModule : Module public Lock Lock { [CanBeNull] - get { return _lock; } + get => _lock; set { if (_lock != null) @@ -54,8 +54,8 @@ public override double Volume { get { - var volume = base.Volume; - var ammo = GetAmmo(); + double volume = base.Volume; + Items.Ammos.Ammo ammo = GetAmmo(); if (ammo != null) { @@ -74,10 +74,10 @@ public override double Volume public double Falloff => falloff.Value; - 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); @@ -114,7 +114,7 @@ public override void AcceptVisitor(IEntityVisitor visitor) public override void UpdateRelatedProperties(AggregateField field) { - var ammo = GetAmmo(); + Items.Ammos.Ammo ammo = GetAmmo(); ammo?.UpdateRelatedProperties(field); base.UpdateRelatedProperties(field); @@ -124,7 +124,7 @@ public bool IsInRange(Position position) { Debug.Assert(ParentRobot != null, "ParentRobot != null"); - return !IsRanged || ParentRobot.IsInRangeOf3D(position,OptimalRange + Falloff); + return !IsRanged || ParentRobot.IsInRangeOf3D(position, OptimalRange + Falloff); } public void ForceUpdate() @@ -142,24 +142,24 @@ public override void UpdateAllProperties() { base.UpdateAllProperties(); - var ammo = GetAmmo(); + Items.Ammos.Ammo ammo = GetAmmo(); ammo?.UpdateAllProperties(); } public override void Unequip(Container container) { - UnequipAmmoToContainer(container); + _ = UnequipAmmoToContainer(container); base.Unequip(container); } public override Dictionary ToDictionary() { - var result = base.ToDictionary(); + Dictionary result = base.ToDictionary(); result.Add(k.ammoCategoryFlags, (long)_ammoCategoryFlags); - var ammo = GetAmmo(); + Items.Ammos.Ammo ammo = GetAmmo(); if (ammo == null) { @@ -178,21 +178,22 @@ protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock uni { if (parentPlayer != null) { - (unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError(); + _ = ((unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError()); + _ = ((unitLockTarget.Target as RemoteControlledCreature)?.Player?.CheckPvp().ThrowIfError()); } } protected Lock GetLock() { - var currentLock = Lock.ThrowIfNull(ErrorCodes.LockTargetNotFound); + Lock currentLock = Lock.ThrowIfNull(ErrorCodes.LockTargetNotFound); - currentLock.State.ThrowIfNotEqual(LockState.Locked, ErrorCodes.LockIsInProgress); + _ = currentLock.State.ThrowIfNotEqual(LockState.Locked, ErrorCodes.LockIsInProgress); if (currentLock is UnitLock unitLockTarget) { IsInRange(unitLockTarget.Target.CurrentPosition).ThrowIfFalse(ErrorCodes.TargetOutOfRange); - var parentPlayer = ParentRobot as Player; + Player parentPlayer = ParentRobot as Player; if (parentPlayer is null && ParentRobot is RemoteControlledCreature) { @@ -223,7 +224,7 @@ protected Lock GetLock() 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) @@ -233,12 +234,12 @@ protected int CreateBeam(Unit target, BeamState beamState, int duration, double 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); + return CreateBeam(location, beamState, duration, bulletTime, 0); } protected override void OnUpdateProperty(AggregateField field) @@ -247,45 +248,45 @@ protected override void OnUpdateProperty(AggregateField field) { case AggregateField.core_usage: case AggregateField.effect_core_usage_gathering_modifier: - { - coreUsage.Update(); + { + coreUsage.Update(); - break; - } + break; + } case AggregateField.cycle_time: case AggregateField.effect_weapon_cycle_time_modifier: case AggregateField.effect_gathering_cycle_time_modifier: - { - cycleTime.Update(); + { + cycleTime.Update(); - break; - } + 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(); + { + optimalRange.Update(); - break; - } + break; + } case AggregateField.falloff: - { - falloff.Update(); + { + falloff.Update(); - break; - } + break; + } } base.OnUpdateProperty(field); } - 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) { @@ -297,8 +298,8 @@ protected double ModifyValueByOptimalRange(Unit target,double value) return 0.0; } - var x = (distance - OptimalRange) / Falloff; - var m = Math.Cos(x * Math.PI) / 2 + 0.5; + double x = (distance - OptimalRange) / Falloff; + double m = (Math.Cos(x * Math.PI) / 2) + 0.5; return value * m; } @@ -307,7 +308,7 @@ 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; } @@ -316,18 +317,18 @@ 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); @@ -351,7 +352,7 @@ private void LockChangedHandler(Lock @lock) return; } - var shutdown = @lock.State == LockState.Disabled || (ED.AttributeFlags.PrimaryLockedTarget && !@lock.Primary); + bool shutdown = @lock.State == LockState.Disabled || (ED.AttributeFlags.PrimaryLockedTarget && !@lock.Primary); if (!shutdown) { @@ -364,7 +365,7 @@ private void LockChangedHandler(Lock @lock) private BeamType GetBeamType() { - var ammo = GetAmmo(); + Items.Ammos.Ammo ammo = GetAmmo(); return ammo != null ? BeamHelper.GetBeamByDefinition(ammo.Definition) @@ -373,14 +374,14 @@ private BeamType GetBeamType() private void SendModuleStateToPlayer() { - var state = State; + IModuleState state = State; if (!(ParentRobot is Player player)) { return; } - var packet = new Packet(ZoneCommand.ModuleChangeState); + Packet packet = new Packet(ZoneCommand.ModuleChangeState); Debug.Assert(ParentComponent != null, "ParentComponent != null"); packet.AppendByte((byte)ParentComponent.Type); @@ -403,8 +404,8 @@ private void SendModuleStateToPlayer() private int CreateBeam(Unit target, BeamState beamState, int duration, double bulletTime, int visibility) { - var delay = 0; - var beamType = GetBeamType(); + int delay = 0; + BeamType beamType = GetBeamType(); if (beamType <= 0) { @@ -420,8 +421,8 @@ private int CreateBeam(Unit target, BeamState beamState, int duration, double bu Debug.Assert(ParentComponent != null, "ParentComponent != null"); - var slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 - var builder = Beam.NewBuilder().WithType(beamType) + int slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 + BeamBuilder builder = Beam.NewBuilder().WithType(beamType) .WithSlot(slot) .WithSource(ParentRobot) .WithState(beamState) @@ -437,8 +438,8 @@ private int CreateBeam(Unit target, BeamState beamState, int duration, double bu private int CreateBeam(Position location, BeamState beamState, int duration, double bulletTime, int visibility) { - var delay = 0; - var beamType = GetBeamType(); + int delay = 0; + BeamType beamType = GetBeamType(); if (beamType <= 0) { @@ -454,8 +455,8 @@ private int CreateBeam(Position location, BeamState beamState, int duration, dou Debug.Assert(ParentComponent != null, "ParentComponent != null"); - var slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 - var builder = Beam.NewBuilder().WithType(beamType) + int slot = ParentComponent.Type == RobotComponentType.Chassis ? Slot : 0xff; // -1 + BeamBuilder builder = Beam.NewBuilder().WithType(beamType) .WithSlot(slot) .WithSource(ParentRobot) .WithState(beamState) @@ -476,7 +477,7 @@ private void SendModuleErrorToPlayer(ErrorCodes error) return; } - var packet = new CombatLogPacket(error, this, _lock); + CombatLogPacket packet = new CombatLogPacket(error, this, _lock); player.Session.SendPacket(packet); } diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 95db3e02e..e3a6f3957 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -1,7 +1,6 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; -using Perpetuum.Log; using Perpetuum.Modules.ModuleProperties; using Perpetuum.Players; using Perpetuum.Units; @@ -24,19 +23,24 @@ public class RemoteControllerModule : ActiveModule 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 - { - get { return bandwidthMax.Value; } - } + 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); - this.AddProperty(bandwidthMax); + 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); } @@ -85,7 +89,7 @@ protected override void OnAction() SyncRemoteChannels(); - var ammo = GetAmmo() as RemoteControlledUnit; + RemoteControlledUnit ammo = GetAmmo() as RemoteControlledUnit; HasFreeBandwidthFor(ammo).ThrowIfFalse(ErrorCodes.MaxBandwidthExceed); @@ -94,7 +98,7 @@ protected override void OnAction() if (ammo.ED.Options.TurretType != TurretType.CombatDrone) { - var myLock = GetLock(); + Lock myLock = GetLock(); if (myLock is TerrainLock) { @@ -117,7 +121,7 @@ protected override void OnAction() .Any() .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); - var r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); + LOSResult r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); if (r.hit) { @@ -131,7 +135,7 @@ protected override void OnAction() targetPosition = GetSpawnPosition(ParentRobot.CurrentPosition); } - var player = this.ParentRobot is Player robotAsPlayer + Player player = ParentRobot is Player robotAsPlayer ? robotAsPlayer : null; @@ -141,8 +145,7 @@ protected override void OnAction() ammo.CheckEnablerExtensionsAndThrowIfFailed(player.Character, ErrorCodes.ExtensionLevelMismatchTerrain); } - RemoteControlledCreature remoteControlledCreature = null; - + RemoteControlledCreature remoteControlledCreature; if (ammo.ED.Options.TurretType == TurretType.Sentry) { remoteControlledCreature = (SentryTurret)Factory.CreateWithRandomEID(ammo.ED.Options.TurretId); @@ -162,7 +165,9 @@ protected override void OnAction() } else { - PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); + _ = PerpetuumException.Create(ErrorCodes.InvalidAmmoDefinition); + + return; } if (player != null) @@ -170,25 +175,23 @@ protected override void OnAction() remoteControlledCreature.SetPlayer(player); } - remoteControlledCreature.Owner = this.ParentRobot.Owner; + remoteControlledCreature.Owner = ParentRobot.Owner; remoteControlledCreature.SetBandwidthUsage(ammo.RemoteChannelBandwidthUsage); UseRemoteChannel(remoteControlledCreature); - var despawnTimeMod = ammo.GetPropertyModifier(AggregateField.despawn_time); - - remoteControlledCreature.DespawnTime = TimeSpan.FromMilliseconds(despawnTimeMod.Value); + remoteControlledCreature.DespawnTime = TimeSpan.FromMilliseconds(lifetime.Value); remoteControlledCreature.SetGroup(bandwidthHandler); - var finder = new ClosestWalkablePositionFinder(Zone, targetPosition); - var position = finder.FindOrThrow(); + ClosestWalkablePositionFinder finder = new ClosestWalkablePositionFinder(Zone, targetPosition); + Position position = finder.FindOrThrow(); remoteControlledCreature.HomePosition = position; - remoteControlledCreature.HomeRange = 50; + remoteControlledCreature.HomeRange = operationalRange.Value; remoteControlledCreature.Orientation = FastRandom.NextInt(0, 3) * 0.25; remoteControlledCreature.CallForHelp = true; - var deployBeamBuilder = Beam.NewBuilder() + BeamBuilder deployBeamBuilder = Beam.NewBuilder() .WithType(BeamType.dock_in) .WithSource(remoteControlledCreature.Player) .WithTarget(remoteControlledCreature) @@ -196,16 +199,14 @@ protected override void OnAction() .WithDuration(TimeSpan.FromSeconds(5)); remoteControlledCreature.AddToZone(Zone, position, ZoneEnterType.Default, deployBeamBuilder); - Logger.Info($"[Remote Control] - spawned turret {remoteControlledCreature.Eid} of type {ammo.ED.Options.TurretType} owned by {remoteControlledCreature.Owner} represented by player {remoteControlledCreature.Player} at {targetPosition}"); - ConsumeAmmo(); } private Position GetSpawnPosition(Position spawnOrigin) { - var spawnRangeMin = SpawnRangeMin; - var spawnRangeMax = SpawnRangeMax; - var spawnPosition = spawnOrigin.GetRandomPositionInRange2D(spawnRangeMin, spawnRangeMax).Clamp(Zone.Size); + double spawnRangeMin = SpawnRangeMin; + double spawnRangeMax = SpawnRangeMax; + Position spawnPosition = spawnOrigin.GetRandomPositionInRange2D(spawnRangeMin, spawnRangeMax).Clamp(Zone.Size); return spawnPosition; } diff --git a/src/Perpetuum/Modules/Weapons/WeaponModule.cs b/src/Perpetuum/Modules/Weapons/WeaponModule.cs index 7fa3eb8bf..0b4a31243 100644 --- a/src/Perpetuum/Modules/Weapons/WeaponModule.cs +++ b/src/Perpetuum/Modules/Weapons/WeaponModule.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Perpetuum.EntityFramework; +using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Modules.ModuleProperties; using Perpetuum.Units; @@ -10,6 +7,9 @@ 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 { @@ -19,16 +19,12 @@ public class WeaponModule : ActiveModule public ModuleProperty DamageModifier { get; } - public ModuleProperty RemoteControlDamageModifier { get; } - public ModuleProperty Accuracy { get; } public WeaponModule(CategoryFlags ammoCategoryFlags) : base(ammoCategoryFlags, true) { _action = new ModuleAction(this); - DamageModifier = new ModuleProperty(this,AggregateField.damage_modifier); - AddProperty(DamageModifier); - RemoteControlDamageModifier = new ModuleProperty(this, AggregateField.remote_control_damage_modifier); + DamageModifier = new ModuleProperty(this, AggregateField.damage_modifier); AddProperty(DamageModifier); Accuracy = new ModuleProperty(this, AggregateField.accuracy); AddProperty(Accuracy); @@ -51,8 +47,8 @@ protected override void OnAction() protected virtual bool CheckAccuracy(Unit victim) { - var rnd = FastRandom.NextDouble(); - var isMiss = rnd * Accuracy.Value > victim.SignatureRadius; + double rnd = FastRandom.NextDouble(); + bool isMiss = rnd * Accuracy.Value > victim.SignatureRadius; return isMiss; } @@ -68,7 +64,7 @@ protected virtual IDamageBuilder GetDamageBuilder() private IEnumerable GetCleanDamages() { - var ammo = (WeaponAmmo)GetAmmo(); + WeaponAmmo ammo = (WeaponAmmo)GetAmmo(); return ammo != null ? ammo.GetCleanDamages() : new Damage[0]; } @@ -86,7 +82,7 @@ public void DoAction() { _weapon.ParentRobot.HasShieldEffect.ThrowIfTrue(ErrorCodes.ShieldIsActive); - var currentLock = _weapon.GetLock(); + Lock currentLock = _weapon.GetLock(); currentLock?.AcceptVisitor(this); } @@ -98,12 +94,12 @@ 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) { @@ -113,7 +109,7 @@ public void VisitUnitLock(UnitLock unitLock) victim.IsInvulnerable.ThrowIfTrue(ErrorCodes.TargetIsInvulnerable); _weapon.ConsumeAmmo(); - var result = _weapon.GetLineOfSight(victim); + LOSResult result = _weapon.GetLineOfSight(victim); if (result.hit) { @@ -123,40 +119,40 @@ public void VisitUnitLock(UnitLock unitLock) 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(); + IDamageBuilder builder = _weapon.GetDamageBuilder(); - Task.Delay(flyTime).ContinueWith(t => victim.TakeDamage(builder.Build())); + _ = 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; + BlockingInfo blockingInfo = _weapon?.ParentRobot?.Zone?.Terrain.Blocks.GetValue(terrainLock.Location) ?? BlockingInfo.None; location = location.AddToZ(Math.Min(blockingInfo.Height, 20)); - var losResult = _weapon.GetLineOfSight(location); + LOSResult losResult = _weapon.GetLineOfSight(location); if (losResult.hit && !location.IsEqual2D(losResult.position)) { @@ -169,28 +165,28 @@ 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); - var beamTime = (int)Math.Max(flyTime, _weapon.CycleTime.TotalMilliseconds); + 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); flyTime += _weapon.CreateBeam(location, BeamState.Hit, beamTime, bulletTime); - var damage = _weapon.GetDamageBuilder().Build().CalculatePlantDamages(); + double damage = _weapon.GetDamageBuilder().Build().CalculatePlantDamages(); if (damage <= 0.0) { return; } - var zone = _weapon.Zone; + IZone 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) diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index f4c5237e8..0dd3f0c3d 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -38,6 +38,14 @@ public void Visit(Player player) return; } + if (smartCreature is RemoteControlledCreature remoteControlledCreature && + player.Zone.Configuration.IsAlpha && + !player.HasPvpEffect && + !remoteControlledCreature.Player.HasPvpEffect) + { + return; + } + if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == player.Eid)) { return; @@ -48,7 +56,7 @@ public void Visit(Player player) return; } - var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); smartCreature.AddThreat(player, new Threat(ThreatType.Bodypull, threat)); } @@ -65,18 +73,18 @@ public void Visit(AreaBomb bomb) return; } - var group = smartCreature.Group; + Flocks.ISmartCreatureGroup group = smartCreature.Group; if (group != null && group.Members.Any(m => m.ThreatManager.Contains(bomb))) { return; } - var threat = Threat.BODY_PULL; + double threat = Threat.BODY_PULL; if (smartCreature.ThreatManager.IsThreatened) { - var h = smartCreature.ThreatManager.GetMostHatedHostile(); + Hostile h = smartCreature.ThreatManager.GetMostHatedHostile(); if (h != null) { @@ -110,7 +118,7 @@ public void Visit(Npc npc) return; } - var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); smartCreature.AddThreat(npc, new Threat(ThreatType.Bodypull, threat)); } @@ -138,7 +146,7 @@ public void Visit(SentryTurret sentryTurret) return; } - var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); smartCreature.AddThreat(sentryTurret, new Threat(ThreatType.Bodypull, threat)); } @@ -166,7 +174,7 @@ public void Visit(CombatDrone combatDrone) return; } - var threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); smartCreature.AddThreat(combatDrone, new Threat(ThreatType.Bodypull, threat)); } diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 45f85a1e9..279e3c426 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -1,6 +1,7 @@ using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Items; +using Perpetuum.Players; using Perpetuum.StateMachines; using Perpetuum.Timers; using Perpetuum.Units; @@ -8,20 +9,19 @@ 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; -using Perpetuum.Zones.Terrains.Materials; -using Perpetuum.Zones.Terrains; -using Perpetuum.Zones.NpcSystem.AI.Behaviors; -using Perpetuum.Zones.Terrains.Materials.Plants; -using Perpetuum.Players; -using Perpetuum.Zones.NpcSystem.AI.CombatDrones; namespace Perpetuum.Zones.NpcSystem { @@ -30,9 +30,6 @@ public class SmartCreature : Creature private const double AggroRange = 30; private const double BestComnatRangeModifier = 0.9; private const double BaseCallForHelpArmorThreshold = 0.2; - private readonly ThreatManager threatManager; - private readonly PseudoThreatManager pseudoThreatManager; - private readonly IndustrialValueManager industrialValueManager; 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)); @@ -51,45 +48,21 @@ public class SmartCreature : Creature public Position HomePosition { get; set; } - public ThreatManager ThreatManager - { - get { return this.threatManager; } - } + public ThreatManager ThreatManager { get; } - public PseudoThreatManager PseudoThreatManager - { - get { return pseudoThreatManager; } - } + public PseudoThreatManager PseudoThreatManager { get; } - public IndustrialValueManager IndustrialValueManager - { - get { return industrialValueManager; } - } + public IndustrialValueManager IndustrialValueManager { get; } - public int BestActionRange - { - get { return this.optimalActionRange.Value; } - } + public int BestActionRange => optimalActionRange.Value; - public int MaxActionRange - { - get { return this.maxActionRange.Value; } - } + public int MaxActionRange => maxActionRange.Value; - public bool IsInHomeRange - { - get { return CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); } - } + public bool IsInHomeRange => CurrentPosition.IsInRangeOf2D(HomePosition, HomeRange); - public ISmartCreatureGroup Group - { - get { return group; } - } + public ISmartCreatureGroup Group => group; - public virtual bool IsStationary - { - get { return MaxSpeed.IsZero(); } - } + public virtual bool IsStationary => MaxSpeed.IsZero(); public virtual double CallForHelpArmorThreshold => BaseCallForHelpArmorThreshold; @@ -101,15 +74,15 @@ public SmartCreature() { RecalculateMaxCombatRange(); RecalculateOptimalCombatRange(); - threatManager = new ThreatManager(); + ThreatManager = new ThreatManager(); AI = new StackFSM(); - pseudoThreatManager = new PseudoThreatManager(); - industrialValueManager = new IndustrialValueManager(); + PseudoThreatManager = new PseudoThreatManager(); + IndustrialValueManager = new IndustrialValueManager(); } public virtual void LookingForHostiles() { - foreach (var visibility in GetVisibleUnits()) + foreach (IUnitVisibility visibility in GetVisibleUnits()) { AddBodyPullThreat(visibility.Target); } @@ -119,36 +92,35 @@ public void LookingForMiningTargets() { IndustrialValueManager.Clear(); - var area = Zone.CreateArea(CurrentPosition, BestActionRange); + Area area = Zone.CreateArea(CurrentPosition, BestActionRange); - var availableMaterialTypes = Zone.Terrain.GetAvailableMineralTypes(); + MaterialType[] availableMaterialTypes = Zone.Terrain.GetAvailableMineralTypes(); foreach (MaterialType materialType in availableMaterialTypes) { - var builder = MineralScanResultBuilder.Create(Zone, materialType); + MineralScanResultBuilder builder = MineralScanResultBuilder.Create(Zone, materialType); builder.ScanArea = area; builder.ScanAccuracy = 100.0; - var result = builder.Build(); + MineralScanResult result = builder.Build(); - MineralLayer mineralLayer = Zone.Terrain.GetMaterialLayer(materialType) as MineralLayer; - if (mineralLayer != null) + if (Zone.Terrain.GetMaterialLayer(materialType) is MineralLayer mineralLayer) { if (result.FoundAny) { - var valuablePositions = result.Area + System.Collections.Generic.IEnumerable valuablePositions = result.Area .GetPositions() .Where(x => mineralLayer.HasMineral(x)) - .Select(x=>Zone.FixZ(x)) - .Where(x => x.IsInRangeOf3D(this.PositionWithHeight, this.BestActionRange)); + .Select(x => Zone.FixZ(x)) + .Where(x => x.IsInRangeOf3D(PositionWithHeight, BestActionRange)); - foreach (var valuablePosition in valuablePositions) + foreach (Position valuablePosition in valuablePositions) { - var mineralNode = mineralLayer.GetNode(valuablePosition); + MineralNode mineralNode = mineralLayer.GetNode(valuablePosition); - IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Mineral, mineralNode.GetValue(valuablePosition)); + _ = IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Mineral, mineralNode.GetValue(valuablePosition)); } } } @@ -159,28 +131,28 @@ public void LookingForHarvestingTargets() { IndustrialValueManager.Clear(); - var area = Zone.CreateArea(CurrentPosition, BestActionRange); + Area area = Zone.CreateArea(CurrentPosition, BestActionRange); - var availablePlantTypes = Zone.Terrain.GetAvailablePlantTypes(); + PlantType[] availablePlantTypes = Zone.Terrain.GetAvailablePlantTypes(); foreach (PlantType plantType in availablePlantTypes) { - var plants = Zone.GetPlantPositionsInArea(plantType, area); + System.Collections.Generic.List plants = Zone.GetPlantPositionsInArea(plantType, area); if (plants.Any()) { - var valuablePositions = plants + System.Collections.Generic.IEnumerable valuablePositions = plants .Select(x => Zone.FixZ(x)) - .Where(x => x.IsInRangeOf2D(this.CurrentPosition, this.BestActionRange)); + .Where(x => x.IsInRangeOf2D(CurrentPosition, BestActionRange)); - foreach (var valuablePosition in valuablePositions) + foreach (Position valuablePosition in valuablePositions) { - var plant = Zone.Terrain.Plants.GetValue(valuablePosition.intX, valuablePosition.intY); + PlantInfo plant = Zone.Terrain.Plants.GetValue(valuablePosition.intX, valuablePosition.intY); if (plant.material > 0) { - IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Plant, plant.material); - } + _ = IndustrialValueManager.GetOrAddIndustrialTargetWithValue(valuablePosition.Center, IndustrialValueType.Plant, plant.material); + } } } } @@ -188,7 +160,7 @@ public void LookingForHarvestingTargets() public void AddPseudoThreat(Unit hostile) { - pseudoThreatManager.AddOrRefreshExisting(hostile); + PseudoThreatManager.AddOrRefreshExisting(hostile); } public void SetGroup(ISmartCreatureGroup group) @@ -221,7 +193,7 @@ protected override void OnPropertyChanged(ItemProperty property) } case AggregateField.armor_current: { - var percentage = Armor.Ratio(ArmorMax); + double percentage = Armor.Ratio(ArmorMax); if (percentage <= CallForHelpArmorThreshold) { @@ -240,9 +212,8 @@ protected override void OnUnitLockStateChanged(Lock @lock) return; } - var unitLock = @lock as UnitLock; - if (unitLock == null) + if (!(@lock is UnitLock unitLock)) { return; } @@ -257,7 +228,7 @@ protected override void OnUnitLockStateChanged(Lock @lock) return; } - var threatValue = unitLock.Primary + double threatValue = unitLock.Primary ? Threat.LOCK_PRIMARY : Threat.LOCK_SECONDARY; @@ -283,7 +254,7 @@ protected void AddBodyPullThreat(Unit enemy) return; } - var helper = new BodyPullThreatHelper(this); + BodyPullThreatHelper helper = new BodyPullThreatHelper(this); enemy.AcceptVisitor(helper); } @@ -317,14 +288,19 @@ public bool IsInAggroRange(Unit target) return this is CombatDrone || IsStationary || IsInRangeOf3D(target, AggroRange); } - public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) + 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); + ThreatManager.GetOrAddHostile(hostile).AddThreat(threat); RemovePseudoThreat(hostile); @@ -333,15 +309,15 @@ public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) return; } - var group = Group; + ISmartCreatureGroup group = Group; if (@group == null) { return; } - var multipliedThreat = Threat.Multiply(threat, 0.5); + Threat multipliedThreat = Threat.Multiply(threat, 0.5); - foreach (var member in @group.Members) + foreach (SmartCreature member in @group.Members) { if (member == this) { @@ -362,22 +338,12 @@ public void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) /// If the target can be a threat public bool CanAddThreatTo(Unit target, Threat threat) { - if (threatManager.Contains(target)) - { - return true; - } - - if (Behavior.Type == BehaviorType.Passive) - { - return false; - } - - return threat.type != ThreatType.Undefined; + 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)) + if (!ThreatManager.Contains(target)) { return; } @@ -443,7 +409,7 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) { base.OnDamageTaken(source, e); - var player = Zone.ToPlayerOrGetOwnerPlayer(source); + Player player = Zone.ToPlayerOrGetOwnerPlayer(source); if (player == null) { @@ -465,16 +431,16 @@ protected override void OnUpdate(TimeSpan time) private void RemovePseudoThreat(Unit hostile) { - pseudoThreatManager.Remove(hostile); + PseudoThreatManager.Remove(hostile); } private void UpdatePseudoThreats(TimeSpan time) { - pseudoUpdateFreq.Update(time); + _ = pseudoUpdateFreq.Update(time); if (pseudoUpdateFreq.Passed) { - pseudoThreatManager.Update(pseudoUpdateFreq.Elapsed); + PseudoThreatManager.Update(pseudoUpdateFreq.Elapsed); pseudoUpdateFreq.Reset(); } } @@ -491,14 +457,14 @@ private void CallingForHelp() return; } - var group = Group; + ISmartCreatureGroup group = Group; if (group == null) { return; } - foreach (var member in group.Members.Where(flockMember => flockMember != this)) + foreach (SmartCreature member in group.Members.Where(flockMember => flockMember != this)) { member.HelpingFor(this); } @@ -513,7 +479,7 @@ private void HelpingFor(SmartCreature caller) ThreatManager.Clear(); - foreach (var hostile in caller.ThreatManager.Hostiles) + foreach (Hostile hostile in caller.ThreatManager.Hostiles) { AddThreat(hostile.Unit, new Threat(ThreatType.Undefined, hostile.Threat), true); } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 26d5499b8..8259b4cde 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -1,8 +1,8 @@ -using Perpetuum.Items; -using Perpetuum.Players; +using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using System; namespace Perpetuum.Zones.RemoteControl @@ -11,7 +11,6 @@ public class RemoteControlledCreature : SmartCreature { private const double SentryTurretCallForHelpArmorThreshold = 0.8; private UnitDespawnHelper despawnHelper; - private ItemProperty remoteChannelBandwidthUsage = ItemProperty.None; private readonly IStandingHandler standingHandler; private const double StandingLimit = 0.0; @@ -23,10 +22,7 @@ public class RemoteControlledCreature : SmartCreature public TimeSpan DespawnTime { - set - { - despawnHelper = UnitDespawnHelper.Create(this, value); - } + set => despawnHelper = UnitDespawnHelper.Create(this, value); } public override bool IsStationary => true; @@ -48,6 +44,16 @@ public void SetBandwidthUsage(double value) RemoteChannelBandwidthUsage = value; } + public override void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) + { + if (hostile.IsPlayer() && Player == (hostile as Player)) + { + return; + } + + base.AddThreat(hostile, threat, spreadToGroup); + } + protected override void OnUpdate(TimeSpan time) { base.OnUpdate(time); @@ -59,13 +65,13 @@ protected override void OnBeforeRemovedFromZone(IZone zone) RemoteChannelDeactivated(this); } - protected override void OnDead(Unit killer) - { - base.OnDead(killer); - } - protected bool IsHostilePlayer(Player targetPlayer) { + if (Player == targetPlayer) + { + return false; + } + if (Zone.Configuration.IsAlpha && !Player.HasPvpEffect && !targetPlayer.HasPvpEffect) { return false; @@ -81,21 +87,16 @@ protected bool IsHostilePlayer(Player targetPlayer) return false; } - var corporationStanding = standingHandler.GetStanding(Player.CorporationEid, targetPlayer.CorporationEid); + double corporationStanding = standingHandler.GetStanding(Player.CorporationEid, targetPlayer.CorporationEid); if (corporationStanding > StandingLimit) { return false; } - var personalStanding = standingHandler.GetStanding(Owner, targetPlayer.Character.Eid); - - if (personalStanding > StandingLimit) - { - return false; - } + double personalStanding = standingHandler.GetStanding(Owner, targetPlayer.Character.Eid); - return true; + return personalStanding <= StandingLimit; } } } diff --git a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs index b2a22ae62..05963940c 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,48 +14,56 @@ public abstract class ItemScannerModule : ActiveModule { protected ItemScannerModule() : base(true) { - + } protected override void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) { - if (unitLockTarget.Target is Player targetPlayer) + Player targetPlayer = unitLockTarget.Target is Player player + ? player + : unitLockTarget.Target is RemoteControlledCreature remoteCreature + ? remoteCreature.Player + : null; + + if (targetPlayer == null) + { + return; + } + + if (targetPlayer.HasPvpEffect || !targetPlayer.Zone.Configuration.Protected) { - if (targetPlayer != null && targetPlayer.HasPvpEffect || !targetPlayer.Zone.Configuration.Protected) - { - return; - } - if (parentPlayer != null) - { - parentPlayer.CheckPvp().ThrowIfError(); - } + 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/ZoneExtensions.cs b/src/Perpetuum/Zones/ZoneExtensions.cs index 6f4375d21..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,7 +69,7 @@ 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) @@ -77,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); @@ -108,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); } @@ -136,18 +145,18 @@ public static List FindWalkableArea(this IZone zone,Point startPosition, /// True if tiles checked are walkable public static bool CheckLinearPath(this IZone zone, Point start, Point end, double slope = 4.0) { - var x = start.X; - var y = start.Y; - var deltaX = Math.Abs(end.X - x); - var deltaY = Math.Abs(end.Y - y); - var travelDist = deltaX + deltaY; - var xIncrement = (end.X > x) ? 1 : -1; - var yIncrement = (end.Y > y) ? 1 : -1; - var error = deltaX - deltaY; + 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 (var i = 0; i <= travelDist; i++) + + for (int i = 0; i <= travelDist; i++) { if (!zone.IsWalkable(x, y, slope)) { @@ -170,19 +179,19 @@ public static bool CheckLinearPath(this IZone zone, Point start, Point end, doub 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)) { @@ -192,36 +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); - //.Where(x=>x.HasTeleportSicknessEffect); + 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); } @@ -246,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; + } } } @@ -264,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 From 0bc6b7631dd120aeaae484c6a26a825f68dfbfa8 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Tue, 6 Feb 2024 18:30:40 +0500 Subject: [PATCH 442/457] - Using RESPEC token doesn't cripple sparks anymore --- .../Accounting/Characters/Character.cs | 335 +++++++++--------- 1 file changed, 168 insertions(+), 167 deletions(-) diff --git a/src/Perpetuum/Accounting/Characters/Character.cs b/src/Perpetuum/Accounting/Characters/Character.cs index e0d8d0775..178baac67 100644 --- a/src/Perpetuum/Accounting/Characters/Character.cs +++ b/src/Perpetuum/Accounting/Characters/Character.cs @@ -1,12 +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 Newtonsoft.Json.Linq; -using System.Windows.Input; using Perpetuum.Common.Loggers.Transaction; using Perpetuum.Containers; using Perpetuum.Data; @@ -25,12 +16,19 @@ 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; @@ -92,15 +90,15 @@ public static bool IsSystemCharacter(Character c) 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, @@ -127,7 +125,9 @@ public Character(int id,IAccountManager accountManager, _walletHelper = walletHelper; if (id <= 0) + { id = 0; + } Id = id; } @@ -168,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); } } @@ -287,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 { @@ -302,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); } } @@ -319,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); @@ -329,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 @@ -379,29 +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(this.Id.ToString(), extensions, new TimeSpan(0,1,0)); + 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() @@ -413,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) @@ -457,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) @@ -479,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); } @@ -508,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); } @@ -522,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) @@ -546,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()) @@ -559,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) @@ -568,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! @@ -578,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); } @@ -593,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); } @@ -617,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, @@ -630,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) @@ -647,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) @@ -663,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); @@ -725,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() @@ -758,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 @@ -773,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)); } @@ -795,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); } @@ -817,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)); } @@ -839,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); } @@ -855,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)); } @@ -880,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()); } @@ -913,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; @@ -926,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) { @@ -952,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) @@ -966,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}, @@ -983,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); } @@ -993,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() @@ -1005,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); @@ -1047,7 +1046,7 @@ public Gang GetGang() [CanBeNull] public Alliance GetAlliance() { - var allianceEid = AllianceEid; + long allianceEid = AllianceEid; return allianceEid == 0L ? null : Alliance.GetOrThrow(allianceEid); } @@ -1058,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); } @@ -1084,17 +1083,17 @@ 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")}, @@ -1122,7 +1121,7 @@ public IDictionary GetFullProfile() {k.isInTraining, isInTraining} }; - _techTreeService.AddInfoToDictionary(Eid,profile); + _techTreeService.AddInfoToDictionary(Eid, profile); return profile; } @@ -1134,7 +1133,7 @@ public PrivateCorporation GetPrivateCorporationOrThrow() [CanBeNull] public PrivateCorporation GetPrivateCorporation() { - return (GetCorporation() as PrivateCorporation); + return GetCorporation() as PrivateCorporation; } public Corporation GetCorporation() @@ -1144,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); From 0ec33e171110ee999f777cfdd8ab0231d638cd35 Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:23:23 +0300 Subject: [PATCH 443/457] Update build.yml (#482) - update actions to use Node.js 20 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da7eaa933..7fb8525be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,13 +31,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + 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@v1.1 + uses: microsoft/setup-msbuild@v2 # Restore the application to populate the obj folder with RuntimeIdentifiers - name: Restore&Build the application for (${{ matrix.configuration }}) From 35a50253da1c09c6ebdd631ca5f31eb8e4d91304 Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:32:11 +0300 Subject: [PATCH 444/457] Update build.yml - update upload-artifact actions to use Node.js 20 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fb8525be..8da04976c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && always()}} - name: Upload a Build Artifact for (${{ matrix.configuration }}) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Perpetuum-Server-${{ github.sha }}.${{ matrix.configuration }} path: ${{ env.Workspace }}/bin/x64/${{ env.Configuration }}/Perpetuum.Server From 6b419fc996fd0dae4a5d7dfff51c739f2d5d4e8f Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Thu, 8 Feb 2024 19:23:08 +0500 Subject: [PATCH 445/457] - New category flag added for electronic CTs. Existing CTs staying where they were so far --- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1317 +++++++++--------- 1 file changed, 659 insertions(+), 658 deletions(-) diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 47cf939b9..189eb1f9a 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -11,711 +11,712 @@ 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_misc = 0x000000000403049B, - cf_respec_tokens = 0x000000000004049B, - cf_spark_teleport_devices = 0x000000000005049B, + 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_misc = 0x000000000403049B, + cf_respec_tokens = 0x000000000004049B, + cf_spark_teleport_devices = 0x000000000005049B, } } From 89d8727d64cc176bc231a60017d2fbb7f4bed84a Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Sat, 10 Feb 2024 09:22:27 +0300 Subject: [PATCH 446/457] Update ProductionFacility.cs (#477) - transfer of protection against negative points to the ProductionFacility class from Repair class. --- .../Services/ProductionEngine/Facilities/Mill.cs | 4 ++-- .../ProductionEngine/Facilities/ProductionFacility.cs | 10 ++++++++-- .../Services/ProductionEngine/Facilities/Prototyper.cs | 4 ++-- .../Services/ProductionEngine/Facilities/Refinery.cs | 2 +- .../Services/ProductionEngine/Facilities/Repair.cs | 6 +----- .../ProductionEngine/Facilities/Reprocessor.cs | 2 +- .../ProductionEngine/Facilities/ResearchLab.cs | 6 +++--- .../Services/ProductionEngine/PBSFacilities.cs | 6 +++--- 8 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs index be60dc61f..be5b3cc6b 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Mill.cs @@ -91,7 +91,7 @@ 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); @@ -101,7 +101,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)(facilityPoints + extensionComponent + standingComponent); diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs index c0aa128bf..8895a41c5 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/ProductionFacility.cs @@ -218,9 +218,15 @@ public virtual bool IsOpen } } - private int GetStandingPoints(Character character) + protected int GetStandingPoints(Character character) { - return (int)( GetStandingOfOwnerToCharacter(character)*20); + var standing = GetStandingOfOwnerToCharacter(character); + if (standing < 0.0) + { + standing = 0.0; + } + + return (int)(standing * 20); } //Overload method for price/sec diff --git a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs index b9fd22aa8..34151e596 100644 --- a/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs +++ b/src/Perpetuum/Services/ProductionEngine/Facilities/Prototyper.cs @@ -172,7 +172,7 @@ public long CalculatePrototypePrice(int prototypeTime, int targetDefinition) 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); @@ -181,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 d9b9c3da0..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 ); diff --git a/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs b/src/Perpetuum/Services/ProductionEngine/PBSFacilities.cs index 79754aab9..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(); @@ -729,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(); @@ -826,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); From a0233701e5232eb5c01749b61dddac72e28077dd Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sun, 11 Feb 2024 11:48:19 +0500 Subject: [PATCH 447/457] - Logic altered to allow NPC Spectators - RCU turrets are now can't be spawned in 1km radius near terminals and teleports - Some code auto-cleanup performed --- .../PerpetuumBootstrapper.cs | 2305 ++++++++--------- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1 + src/Perpetuum/Modules/ActiveModule.cs | 10 +- src/Perpetuum/Modules/DrillerModule.cs | 84 +- src/Perpetuum/Modules/HarvesterModule.cs | 60 +- .../Modules/RemoteControllerModule.cs | 32 +- src/Perpetuum/Players/Player.cs | 256 +- src/Perpetuum/Robots/Robot.cs | 135 +- src/Perpetuum/Zones/DistanceConstants.cs | 26 +- src/Perpetuum/Zones/Effects/GangEffect.cs | 19 +- src/Perpetuum/Zones/LandMines/LandMine.cs | 31 +- .../NpcSystem/AI/BodyPullThreatHelper.cs | 3 +- .../Zones/NpcSystem/AI/ModuleActivator.cs | 70 +- .../TargettingStrategies/CombatStrategies.cs | 2 +- .../Zones/RemoteControl/CombatDrone.cs | 31 +- .../RemoteControl/RemoteControlledCreature.cs | 24 +- .../Zones/RemoteControl/SentryTurret.cs | 6 +- .../Scanning/Modules/ItemScannerModule.cs | 6 +- src/Perpetuum/Zones/ZoneExtensions.Gang.cs | 2 +- 19 files changed, 1508 insertions(+), 1595 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index 96972f9ba..c89036b08 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -119,6 +119,7 @@ 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; @@ -143,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; @@ -186,25 +188,23 @@ using TrashItems = Perpetuum.RequestHandlers.TrashItems; using UnpackItems = Perpetuum.RequestHandlers.UnpackItems; using UnstackAmount = Perpetuum.RequestHandlers.UnstackAmount; -using Perpetuum.Zones.LandMines; -using Perpetuum.Zones.RemoteControl; 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; } @@ -220,19 +220,19 @@ 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); } @@ -243,25 +243,27 @@ public IContainer GetContainer() 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()); @@ -274,12 +276,13 @@ public IEnumerable GetCommands() public void Init(string gameRoot) { + Thread.Sleep(10000); _builder = new ContainerBuilder(); InitContainer(gameRoot); _container = _builder.Build(); Logger.Current = _container.Resolve>(); - var config = _container.Resolve(); + GlobalConfiguration config = _container.Resolve(); _container.Resolve().State = HostState.Init; @@ -290,7 +293,7 @@ public void Init(string gameRoot) 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); @@ -381,16 +384,18 @@ public void Init(string gameRoot) 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!"); @@ -399,7 +404,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}"); }); @@ -408,7 +413,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); } @@ -429,12 +434,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); } @@ -451,30 +456,30 @@ private IRegistrationBuilder(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)); + _ = 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; - }); + }; }); } @@ -498,268 +503,262 @@ private void InitContainer(string gameRoot) 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(); - _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().SingleInstance().AutoActivate(); + _ = _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().SingleInstance(); + _ = _builder.RegisterType().SingleInstance(); - _builder.RegisterType(); - _builder.RegisterType().As(); - _builder.RegisterType().As().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.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(); // this doesn't appear to be something that should be a singleton. + _ = _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.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); + _ = _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(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + 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); + _ = _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.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.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 => + _ = _builder.Register(x => { - var ctx = x.Resolve(); + 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 @@ -795,13 +794,13 @@ private IRegistrationBuilder() where T : Corporation { - _builder.RegisterType(); - RegisterEntity().PropertiesAutowired(); + _ = _builder.RegisterType(); + _ = RegisterEntity().PropertiesAutowired(); } protected void RegisterProximityDevices() where T : ProximityDeviceBase { - RegisterUnit(); + _ = RegisterUnit(); } private IRegistrationBuilder RegisterModule() where T : Module @@ -811,12 +810,12 @@ private IRegistrationBuilder() where T : EffectModule { - RegisterModule(); + _ = RegisterModule(); } private void RegisterProductionFacility() where T : ProductionFacility { - RegisterEntity().PropertiesAutowired(); + _ = RegisterEntity().PropertiesAutowired(); } public IRegistrationBuilder RegisterRobot() where T : Robot @@ -868,8 +867,8 @@ private void RegisterEntities() 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)); #endif @@ -1055,9 +1054,9 @@ private void RegisterEntities() _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 { @@ -1066,7 +1065,7 @@ void ByDefinition(int definition, params Parameter[] parameters) where T : En 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); } @@ -1074,7 +1073,7 @@ void ByCategoryFlags(CategoryFlags cf, params Parameter[] parameters) where T 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); } @@ -1083,10 +1082,10 @@ void ByName(string name, params Parameter[] parameters) where T : Entity void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] parameters) where T : Entity { //TODO: this might be expensive -- string matching all defaults - var matches = ctx.Resolve().GetAll() + IEnumerable matches = ctx.Resolve().GetAll() .Where(i => i.CategoryFlags == cf) .Where(i => i.Name.Contains(substr)); - foreach (var ed in matches) + foreach (EntityDefault ed in matches) { ByDefinition(ed.Definition, parameters); } @@ -1181,19 +1180,19 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_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_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); @@ -1202,16 +1201,16 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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_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_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); @@ -1272,7 +1271,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] ByCategoryFlags(CategoryFlags.cf_mission_coin); ByCategoryFlags(CategoryFlags.cf_area_bomb); ByCategoryFlags(CategoryFlags.cf_plasma_bomb); - + ByCategoryFlags(CategoryFlags.cf_proximity_probe); ByCategoryFlags(CategoryFlags.cf_light_landmines); ByCategoryFlags(CategoryFlags.cf_medium_landmines); @@ -1356,20 +1355,11 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] 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; @@ -1381,7 +1371,7 @@ void ByNamePatternAndFlag(string substr, CategoryFlags cf, params Parameter[] private void RegisterLoggers() { - _builder.Register(x => + _ = _builder.Register(x => { return new LoggerCache(new MemoryCache("LoggerCache")) { @@ -1389,76 +1379,79 @@ 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(); + 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<>)); } @@ -1470,7 +1463,7 @@ private IRegistrationBuilder(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(); @@ -1479,66 +1472,66 @@ private void RegisterFlock(PresenceType presenceType) where T : Flock public void RegisterNpcs() { - _builder.RegisterType().As(); - _builder.RegisterType().SingleInstance(); - _builder.RegisterType().SingleInstance().As(); + _ = _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().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().SingleInstance(); + _ = _builder.RegisterType().As().SingleInstance(); - _builder.RegisterType() + _ = _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 => + _ = _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)); - }); + }; }); RegisterFlock(PresenceType.Normal); @@ -1555,32 +1548,34 @@ public void RegisterNpcs() 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.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 => - { - 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)) + { 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 IRoamingPresence roamingPresence) { @@ -1596,7 +1591,7 @@ public void RegisterNpcs() } return p; - }); + }; }); @@ -1604,151 +1599,151 @@ 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(); - _builder.RegisterType().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().As(); - _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().SingleInstance().As(); + _ = _builder.RegisterType().SingleInstance().As(); - _builder.RegisterType().SingleInstance(); + _ = _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(); - _builder.RegisterType().As(); - _builder.RegisterType().As(); - _builder.RegisterType(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); // OPP: EPBonusEventService singleton - _builder.RegisterType().SingleInstance().OnActivated(e => + _ = _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 => + _ = _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()); @@ -1756,41 +1751,41 @@ private void InitRelayManager() e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); e.Instance.AttachListener(e.Context.Resolve()); - var obs = new GameTimeObserver(e.Instance); + GameTimeObserver obs = new GameTimeObserver(e.Instance); obs.Subscribe(e.Context.Resolve()); }); - _builder.RegisterType().As().SingleInstance().OnActivated(e => + _ = _builder.RegisterType().As().SingleInstance().OnActivated(e => { e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(1.5))); }); // OPP: InterzoneNPCManager - RegisterAutoActivate(TimeSpan.FromSeconds(10)); + _ = 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 { - 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); @@ -1805,550 +1800,550 @@ 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.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.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); + _ = 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.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); + _ = 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); + _ = 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 => { - var reader = ctx.Resolve(); - var listener = new OreNpcSpawner(zone, ctx.Resolve(), reader); - var eventListenerService = ctx.Resolve(); + 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 }; } - var nodeGeneratorFactory = new MineralNodeGeneratorFactory(zone); + MineralNodeGeneratorFactory nodeGeneratorFactory = new MineralNodeGeneratorFactory(zone); - var materialLayers = new List(); + List materialLayers = new List(); - foreach (var configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) + foreach (IMineralConfiguration configuration in reader.ReadAll().Where(c => c.ZoneId == zone.Id)) { - var repo = new MineralNodeRepository(zone, configuration.Type); + 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, eventListenerService); + 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, eventListenerService); + LiquidLayer layer = new LiquidLayer(zone.Size.Width, zone.Size.Height, configuration, repo, nodeGeneratorFactory, eventListenerService); layer.LoadMineralNodes(); materialLayers.Add(layer); break; @@ -2362,42 +2357,42 @@ 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); + ushort[] altitude = loader.Load(zone, LayerType.Altitude); AltitudeLayer altitudeLayer; if (zone.Configuration.Terraformable) { - var originalAltitude = new Layer(LayerType.OriginalAltitude, altitude, size.Width, size.Height); - var blend = loader.LoadLayerData(zone, "altitude_blend"); - var blendLayer = new Layer(LayerType.Blend, blend, size.Width, size.Height); + 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 @@ -2410,13 +2405,13 @@ 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(); + 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)); @@ -2431,68 +2426,60 @@ 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 new PvpRiftSpawnPositionFinder(zone); - } - - return new PveRiftSpawnPositionFinder(zone); + return zone.Configuration.Terraformable ? new PvpRiftSpawnPositionFinder(zone) : (RiftSpawnPositionFinder)new PveRiftSpawnPositionFinder(zone); }; }); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { if (zone is TrainingZone) + { return null; + } if (zone is StrongHoldZone) { - var strongHoldExitConfigCount = Db.Query().CommandText("SELECT COUNT(*) FROM strongholdexitconfig WHERE zoneid = @zoneId;") + int strongHoldExitConfigCount = Db.Query().CommandText("SELECT COUNT(*) FROM strongholdexitconfig WHERE zoneid = @zoneId;") .SetParameter("@zoneId", zone.Id) .ExecuteScalar(); - if (strongHoldExitConfigCount < 1) - { - return null; - } - - return ctx.Resolve(new TypedParameter(typeof(IZone), zone)); + return strongHoldExitConfigCount < 1 ? null : (IRiftManager)ctx.Resolve(new TypedParameter(typeof(IZone), zone)); } - var zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") + List zoneConfigs = Db.Query().CommandText("SELECT maxrifts FROM zoneriftsconfig WHERE zoneid = @zoneId") .SetParameter("@zoneId", zone.Id) .Execute(); if (zoneConfigs.Count < 1) @@ -2500,16 +2487,16 @@ private void RegisterRifts() return null; } - var record = zoneConfigs[0]; - var maxrifts = record.GetValue("maxrifts"); + System.Data.IDataRecord record = zoneConfigs[0]; + int maxrifts = record.GetValue("maxrifts"); if (maxrifts < 1) { return null; } - var spawnTime = TimeRange.FromLength(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); - var finder = ctx.Resolve>().Invoke(zone); + 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)); }; }); @@ -2517,14 +2504,14 @@ private void RegisterRifts() private void RegisterRelics() { - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { - var numRelicConfigs = Db.Query().CommandText("SELECT id FROM relicspawninfo WHERE zoneid = @zoneId") + int numRelicConfigs = Db.Query().CommandText("SELECT id FROM relicspawninfo WHERE zoneid = @zoneId") .SetParameter("@zoneId", zone.Id) .Execute().Count; if (numRelicConfigs < 1) @@ -2532,15 +2519,15 @@ private void RegisterRelics() return null; } - var zoneConfigs = Db.Query().CommandText("SELECT maxspawn FROM reliczoneconfig WHERE zoneid = @zoneId") + List zoneConfigs = Db.Query().CommandText("SELECT maxspawn FROM reliczoneconfig WHERE zoneid = @zoneId") .SetParameter("@zoneId", zone.Id) .Execute(); if (zoneConfigs.Count < 1) { return null; } - var record = zoneConfigs[0]; - var maxspawn = record.GetValue("maxspawn"); + System.Data.IDataRecord record = zoneConfigs[0]; + int maxspawn = record.GetValue("maxspawn"); if (maxspawn < 1) { return null; @@ -2553,14 +2540,14 @@ private void RegisterRelics() 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 => @@ -2568,34 +2555,34 @@ private void RegisterZones() e.Context.Resolve().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5))); }).As(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.Register>(x => + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { return new WeatherEventListener(ctx.Resolve(), zone); }; }); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + IComponentContext ctx = x.Resolve(); return zone => { - var listener = new EnvironmentalEffectHandler(zone); + EnvironmentalEffectHandler listener = new EnvironmentalEffectHandler(zone); ctx.Resolve().AttachListener(listener); return listener; }; }); - _builder.RegisterType().AsSelf().As(); - _builder.RegisterType().AsSelf().As(); + _ = _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 @@ -2606,14 +2593,14 @@ private void RegisterZones() }; }); - _builder.RegisterType().As(); - _builder.RegisterType(); - _builder.RegisterType(); - _builder.RegisterType().As().SingleInstance(); + _ = _builder.RegisterType().As(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType().As().SingleInstance(); 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()); }); @@ -2624,27 +2611,27 @@ void RegisterZone(ZoneType type) where T : Zone RegisterZone(ZoneType.Training); RegisterZone(ZoneType.Stronghold); - _builder.RegisterType(); - _builder.RegisterType(); + _ = _builder.RegisterType(); + _ = _builder.RegisterType(); - _builder.RegisterType().As(); + _ = _builder.RegisterType().As(); - _builder.Register>(x => + _ = _builder.Register>(x => { - var ctx = x.Resolve(); + 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); @@ -2679,7 +2666,7 @@ void RegisterZone(ZoneType type) where T : Zone } ctx.Resolve().AttachListener(new NpcReinforcementSpawner(zone, ctx.Resolve())); - var listener = ctx.Resolve>().Invoke(zone); + WeatherEventListener listener = ctx.Resolve>().Invoke(zone); listener.Subscribe(zone.Weather); zone.LoadUnits(); @@ -2687,15 +2674,15 @@ void RegisterZone(ZoneType type) where T : 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); + _ = e.Context.Resolve>().Invoke(zone); Logger.Info("------------------"); Logger.Info("--"); @@ -2704,49 +2691,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().As().SingleInstance(); - _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 @@ -2757,149 +2744,149 @@ private IRegistrationBuilder(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); + _ = 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.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); + _ = 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/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index 189eb1f9a..b02e25500 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -715,6 +715,7 @@ public enum CategoryFlags : long 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/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 24f83031a..360f218d3 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -176,10 +176,10 @@ public override Dictionary ToDictionary() protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) { - if (parentPlayer != null) + if (parentPlayer != null && (unitLockTarget.Target as RemoteControlledCreature)?.CommandRobot is Player player) { _ = ((unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError()); - _ = ((unitLockTarget.Target as RemoteControlledCreature)?.Player?.CheckPvp().ThrowIfError()); + _ = player.CheckPvp().ThrowIfError(); } } @@ -195,9 +195,11 @@ protected Lock GetLock() Player parentPlayer = ParentRobot as Player; - if (parentPlayer is null && ParentRobot is RemoteControlledCreature) + if (parentPlayer is null && + ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player player) { - parentPlayer = (ParentRobot as RemoteControlledCreature).Player; + parentPlayer = player; } if (ED.AttributeFlags.OffensiveModule) diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index 69cbcfc03..fed8937ad 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using System.Transactions; using Perpetuum.Data; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; @@ -18,6 +12,12 @@ 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 { @@ -27,14 +27,14 @@ public class DrillerModule : GathererModule private readonly RareMaterialHandler _rareMaterialHandler; private readonly MaterialHelper _materialHelper; private readonly ItemProperty _miningAmountModifier; - private readonly ISet LIQUIDS = new HashSet(new int[] + 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) + public DrillerModule(CategoryFlags ammoCategoryFlags, RareMaterialHandler rareMaterialHandler, MaterialHelper materialHelper) : base(ammoCategoryFlags, true) { _rareMaterialHandler = rareMaterialHandler; @@ -74,7 +74,7 @@ public List Extract(MineralLayer layer, Point location, uint amount) return new List(); } - var extractor = new MineralExtractor(location, amount, _materialHelper); + MineralExtractor extractor = new MineralExtractor(location, amount, _materialHelper); layer.AcceptVisitor(extractor); @@ -83,9 +83,9 @@ public List Extract(MineralLayer layer, Point location, uint amount) protected override void OnAction() { - var zone = Zone; + IZone zone = Zone; - if ( zone != null) + if (zone != null) { DoExtractMinerals(zone); } @@ -95,7 +95,7 @@ protected override void OnAction() protected override int CalculateEp(int materialType) { - var activeGathererModules = this is RemoteControlledDrillerModule + 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(); @@ -104,9 +104,9 @@ protected override int CalculateEp(int materialType) 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; + 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; @@ -115,19 +115,14 @@ protected override int CalculateEp(int materialType) chance /= 2.0; } - var rand = FastRandom.NextDouble(); + double rand = FastRandom.NextDouble(); - if (rand <= chance) - { - return 1; - } - - return 0; + return rand <= chance ? 1 : 0; } public void DoExtractMinerals(IZone zone) { - var terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); + TerrainLock terrainLock = GetLock().ThrowIfNotType(ErrorCodes.InvalidLockType); MaterialType materialType; @@ -137,9 +132,7 @@ public void DoExtractMinerals(IZone zone) } else { - MiningAmmo ammo = GetAmmo() as MiningAmmo; - - if (ammo == null) + if (!(GetAmmo() is MiningAmmo ammo)) { return; } @@ -147,52 +140,53 @@ public void DoExtractMinerals(IZone zone) materialType = ammo.MaterialType; } - var materialInfo = _materialHelper.GetMaterialInfo(materialType); + MaterialInfo materialInfo = _materialHelper.GetMaterialInfo(materialType); CheckEnablerEffect(materialInfo); - var mineralLayer = zone.Terrain.GetMineralLayerOrThrow(materialInfo.Type); - var materialAmount = materialInfo.Amount * _miningAmountModifier.Value; - var extractedMaterials = Extract(mineralLayer, terrainLock.Location,(uint) materialAmount); + 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.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 is RemoteControlledCreature - ? (ParentRobot as RemoteControlledCreature).Player + Player player = ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer : ParentRobot as Player; - Debug.Assert(player != null,"player != null"); + 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, (int) materialInfo.Type); + OnGathererMaterial(zone, player, (int)materialInfo.Type); Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); } @@ -200,7 +194,7 @@ public void DoExtractMinerals(IZone zone) private void CheckEnablerEffect(MaterialInfo materialInfo) { - if ( !Zone.Configuration.Terraformable ) + if (!Zone.Configuration.Terraformable) { return; } @@ -210,7 +204,7 @@ private void CheckEnablerEffect(MaterialInfo materialInfo) return; } - var containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); + bool containsEnablerEffect = ParentRobot.EffectHandler.ContainsEffect(EffectCategory.effcat_pbs_mining_tower_effect); containsEnablerEffect.ThrowIfFalse(ErrorCodes.MiningEnablerEffectRequired); } diff --git a/src/Perpetuum/Modules/HarvesterModule.cs b/src/Perpetuum/Modules/HarvesterModule.cs index 327e7acb6..652e3616c 100644 --- a/src/Perpetuum/Modules/HarvesterModule.cs +++ b/src/Perpetuum/Modules/HarvesterModule.cs @@ -1,8 +1,4 @@ -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; @@ -15,6 +11,10 @@ using Perpetuum.Zones.RemoteControl; using Perpetuum.Zones.Terrains; using Perpetuum.Zones.Terrains.Materials.Plants.Harvesters; +using System; +using System.Diagnostics; +using System.Linq; +using System.Transactions; namespace Perpetuum.Modules { @@ -24,7 +24,7 @@ public class HarvesterModule : GathererModule 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); @@ -59,7 +59,7 @@ public override void UpdateProperty(AggregateField field) protected override int CalculateEp(int materialType) { - var activeGathererModules = this is RemoteControlledHarvesterModule + 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(); @@ -68,25 +68,20 @@ protected override int CalculateEp(int materialType) 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; + 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; - var rand = FastRandom.NextDouble(); - - if (rand <= chance) - { - return 1; - } + double rand = FastRandom.NextDouble(); - return 0; + return rand <= chance ? 1 : 0; } protected override void OnAction() { - var zone = Zone; + IZone zone = Zone; if (zone == null) { @@ -99,51 +94,52 @@ protected override void OnAction() 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 is RemoteControlledCreature - ? (ParentRobot as RemoteControlledCreature).Player + Player player = ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer : ParentRobot as Player; - Debug.Assert(player != null,"player != null"); + 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); - var extractedHarvestDefinition = extractedMaterial.Definition; - var extractedQuantity = extractedMaterial.Quantity; + int extractedHarvestDefinition = extractedMaterial.Definition; + int extractedQuantity = extractedMaterial.Quantity; player.MissionHandler.EnqueueMissionEventInfo(new HarvestPlantEventInfo(player, extractedHarvestDefinition, extractedQuantity, terrainLock.Location)); player.Zone?.HarvestLogHandler.EnqueueHarvestLog(extractedHarvestDefinition, extractedQuantity); } container.Save(); - OnGathererMaterial(zone, player, (int) plantInfo.type); + OnGathererMaterial(zone, player, (int)plantInfo.type); Transaction.Current.OnCommited(() => container.SendUpdateToOwnerAsync()); scope.Complete(); } diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index e3a6f3957..f11658afb 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -4,6 +4,7 @@ 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; @@ -11,6 +12,7 @@ using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem.AI.Behaviors; using Perpetuum.Zones.RemoteControl; +using Perpetuum.Zones.Teleporting; using System; using System.Linq; @@ -19,7 +21,6 @@ namespace Perpetuum.Modules public class RemoteControllerModule : ActiveModule { private const int SentryTurretHeight = 7; - private const double TurretDeployRange = 2; private const double SpawnRangeMin = 2; private const double SpawnRangeMax = 5; private readonly ModuleProperty bandwidthMax; @@ -115,11 +116,25 @@ protected override void OnAction() return; } + Position spawnPosition = Zone.FixZ(targetPosition); + + Zone.Units + .OfType() + .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_RCU) + .Any() + .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); + + Zone.Units + .OfType() + .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_BASE) + .Any() + .ThrowIfTrue(ErrorCodes.NotDeployableNearObject); + Zone.Units - .OfType() - .WithinRange(Zone.FixZ(targetPosition), TurretDeployRange) - .Any() - .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); + .OfType() + .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_TELEPORT) + .Any() + .ThrowIfTrue(ErrorCodes.TeleportIsInRange); LOSResult r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); @@ -170,10 +185,7 @@ protected override void OnAction() return; } - if (player != null) - { - remoteControlledCreature.SetPlayer(player); - } + remoteControlledCreature.SetCommandRobot(player ?? ParentRobot); remoteControlledCreature.Owner = ParentRobot.Owner; remoteControlledCreature.SetBandwidthUsage(ammo.RemoteChannelBandwidthUsage); @@ -193,7 +205,7 @@ protected override void OnAction() BeamBuilder deployBeamBuilder = Beam.NewBuilder() .WithType(BeamType.dock_in) - .WithSource(remoteControlledCreature.Player) + .WithSource(remoteControlledCreature.CommandRobot) .WithTarget(remoteControlledCreature) .WithState(BeamState.Hit) .WithDuration(TimeSpan.FromSeconds(5)); diff --git a/src/Perpetuum/Players/Player.cs b/src/Perpetuum/Players/Player.cs index 9e1f96080..a224a1c98 100644 --- a/src/Perpetuum/Players/Player.cs +++ b/src/Perpetuum/Players/Player.cs @@ -70,10 +70,7 @@ public sealed class Player : Robot, IBlobableUnit, IBlobEmitter public const int ARKHE_REQUEST_TIMER_MINUTES_PVP = 3; public const int ARKHE_REQUEST_TIMER_MINUTES_NPC = 1; - private bool HasAggressorEffect - { - get { return EffectHandler.ContainsEffect(EffectType.effect_aggressor); } - } + private bool HasAggressorEffect => EffectHandler.ContainsEffect(EffectType.effect_aggressor); public long CorporationEid { get; set; } @@ -85,10 +82,7 @@ private bool HasAggressorEffect public MissionHandler MissionHandler { get; private set; } - 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; } @@ -96,16 +90,12 @@ public bool IsInSafeArea { get { - var zone = Zone; + IZone zone = Zone; - if (zone == null) - { - return false; - } - - return zone.Configuration.Protected || + return zone != null +&& (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area) || - EffectHandler.ContainsEffect(EffectType.effect_safe_spot); + EffectHandler.ContainsEffect(EffectType.effect_safe_spot)); } } @@ -113,31 +103,17 @@ public override bool IsLockable { get { - var isInvulnerable = IsInvulnerable; - - if (isInvulnerable) - { - return false; - } + bool isInvulnerable = IsInvulnerable; - return base.IsLockable; + return !isInvulnerable && base.IsLockable; } } - public IBlobHandler BlobHandler - { - get { return blobHandler; } - } + public IBlobHandler BlobHandler => blobHandler; - public double BlobEmission - { - get { return blobEmitter.BlobEmission; } - } + public double BlobEmission => blobEmitter.BlobEmission; - public double BlobEmissionRadius - { - get { return blobEmitter.BlobEmissionRadius; } - } + public double BlobEmissionRadius => blobEmitter.BlobEmissionRadius; public Player( IExtensionReader extensionReader, @@ -206,20 +182,20 @@ public override void OnUpdateToDb() DynamicProperties.Update(k.currentCore, Core); } - var zone = Zone; + IZone zone = Zone; if (zone == null || States.Dead) { return; } - var character = Character; + Character character = Character; character.ZoneId = zone.Id; character.ZonePosition = CurrentPosition; - var p = DynamicProperties.GetProperty(k.pvpRemaining); - var pvpEffect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); + IDynamicProperty p = DynamicProperties.GetProperty(k.pvpRemaining); + Effect pvpEffect = EffectHandler.GetEffectsByType(EffectType.effect_pvp).FirstOrDefault(); if (pvpEffect == null) { @@ -228,7 +204,7 @@ public override void OnUpdateToDb() return; } - var effectTimer = pvpEffect.Timer; + IntervalTimer effectTimer = pvpEffect.Timer; if (effectTimer != null) { @@ -258,7 +234,7 @@ public void ClearStrongholdDespawn() 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); @@ -269,8 +245,8 @@ public void SendModuleProcessError(Module module, ErrorCodes error) public void ApplyInvulnerableEffect() { RemoveInvulnerableEffect(); // Remove existing effect, set new - var builder = NewEffectBuilder().SetType(EffectType.effect_invulnerable); - builder.WithDurationModifier(0.75); //Reduce span of syndicate protection + EffectBuilder builder = NewEffectBuilder().SetType(EffectType.effect_invulnerable); + _ = builder.WithDurationModifier(0.75); //Reduce span of syndicate protection ApplyEffect(builder); } @@ -281,18 +257,18 @@ public void RemoveInvulnerableEffect() 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); @@ -300,14 +276,14 @@ public void ApplyTeleportSicknessEffect() 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); } - var builder = NewEffectBuilder().SetType(EffectType.effect_teleport_self_enabler).WithDuration(duration); + EffectBuilder builder = NewEffectBuilder().SetType(EffectType.effect_teleport_self_enabler).WithDuration(duration); ApplyEffect(builder); } @@ -326,14 +302,14 @@ public void CheckDockingConditionsAndThrow(long baseEid, bool checkRange = true) HasTeleportSicknessEffect.ThrowIfTrue(ErrorCodes.CantDockThisState); } - var zone = Zone; + IZone zone = Zone; if (zone == null) { return; } - var dockingBase = dockingBaseHelper.GetDockingBase(baseEid); + DockingBase dockingBase = dockingBaseHelper.GetDockingBase(baseEid); if (dockingBase == null) { @@ -345,11 +321,11 @@ public void CheckDockingConditionsAndThrow(long baseEid, bool checkRange = true) 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); @@ -364,7 +340,7 @@ public void DockToBase(IZone zone, DockingBase dockingBase) { States.Dock = true; - var publicContainer = dockingBase.GetPublicContainer(); + PublicContainer publicContainer = dockingBase.GetPublicContainer(); FullArmorRepair(); @@ -376,22 +352,19 @@ public void DockToBase(IZone zone, DockingBase dockingBase) { RemoveFromZone(); MissionHelper.MissionAdvanceDockInTarget(Character.Id, zone.Id, CurrentPosition); - TransportAssignment.DeliverTransportAssignmentAsync(Character); + _ = TransportAssignment.DeliverTransportAssignmentAsync(Character); }); } public ErrorCodes CheckPvp() { - var zone = Zone; + IZone zone = Zone; Debug.Assert(zone != null); - if (!HasPvpEffect && (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area))) - { - return ErrorCodes.PvpIsNotAllowed; - } - - return ErrorCodes.NoError; + return !HasPvpEffect && (zone.Configuration.Protected || EffectHandler.ContainsEffect(EffectType.effect_syndicate_area)) + ? ErrorCodes.PvpIsNotAllowed + : ErrorCodes.NoError; } public bool IsInDefaultCorporation() @@ -401,16 +374,16 @@ public bool IsInDefaultCorporation() public override ItemPropertyModifier GetPropertyModifier(AggregateField field) { - var modifier = base.GetPropertyModifier(field); + ItemPropertyModifier modifier = base.GetPropertyModifier(field); if (Character == Character.None) { return modifier; } - var characterExtensions = Character.GetExtensions(); - var extensions = extensionReader.GetExtensions(); - var extensionBonus = characterExtensions + 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); @@ -421,7 +394,7 @@ public override ItemPropertyModifier GetPropertyModifier(AggregateField field) if (!extensionBonus.IsZero()) { - var m = ItemPropertyModifier.Create(field, extensionBonus); + ItemPropertyModifier m = ItemPropertyModifier.Create(field, extensionBonus); m.NormalizeExtensionBonus(); m.Modify(ref modifier); @@ -474,25 +447,22 @@ public void OnPvpSupport(Unit target) } } - public override string InfoString - { - get { return $"Player:{Character.Id}:{Definition}:{Eid}"; } - } + public override string InfoString => $"Player:{Character.Id}:{Definition}:{Eid}"; public void SendInitSelf() { - var zone = Zone; + IZone zone = Zone; Debug.Assert(zone != null, "zone != null"); Session.SendPacket(EnterPacketBuilder); Session.SendTerrainData(); zone.SendBeamsToPlayer(this, GridDistricts.All); - var lockPackets = GetLockPackets(); + IEnumerable lockPackets = GetLockPackets(); Session.SendPackets(lockPackets); - foreach (var visibility in GetVisibleUnits()) + foreach (IUnitVisibility visibility in GetVisibleUnits()) { Session.SendPacket(visibility.Target.EnterPacketBuilder); @@ -501,7 +471,7 @@ public void SendInitSelf() continue; } - var unitLockPackets = robot.GetLockPackets(); + IEnumerable unitLockPackets = robot.GetLockPackets(); Session.SendPackets(unitLockPackets); } @@ -509,12 +479,12 @@ public void SendInitSelf() Session.SendPacket(new GangUpdatePacketBuilder(Visibility.Visible, zone.GetGangMembers(Gang))); Session.SendPacket(zone.Weather.GetCurrentWeather().CreateUpdatePacket()); - foreach (var effect in EffectHandler.Effects) + foreach (Effect effect in EffectHandler.Effects) { Session.SendPacket(new EffectPacketBuilder(effect, true)); } - foreach (var module in ActiveModules) + foreach (ActiveModule module in ActiveModules) { module.ForceUpdate(); } @@ -522,7 +492,7 @@ public void SendInitSelf() public void WriteFQLog(string message) { - var e = new LogEvent + LogEvent e = new LogEvent { LogType = LogType.Info, Tag = "FQ", @@ -541,23 +511,23 @@ public override void OnCombatEvent(Unit source, CombatEventArgs e) { base.OnCombatEvent(source, e); - var player = Zone.ToPlayerOrGetOwnerPlayer(source); + Player player = Zone.ToPlayerOrGetOwnerPlayer(source); if (player == null) { return; } - var logger = LazyInitializer.EnsureInitialized(ref combatLogger, CreateCombatLogger); + CombatLogger logger = LazyInitializer.EnsureInitialized(ref combatLogger, CreateCombatLogger); logger.Log(player, e); } public static Player LoadPlayerAndAddToZone(IZone zone, Character character) { - using (var scope = Db.CreateTransaction()) + using (TransactionScope scope = Db.CreateTransaction()) { - var player = (Player)character.GetActiveRobot().ThrowIfNull(ErrorCodes.ARobotMustBeSelected); + Player player = (Player)character.GetActiveRobot().ThrowIfNull(ErrorCodes.ARobotMustBeSelected); DockingBase dockingBase = null; ZoneEnterType zoneEnterType; @@ -575,19 +545,19 @@ public static Player LoadPlayerAndAddToZone(IZone zone, Character character) else { zoneEnterType = ZoneEnterType.Teleport; - zone.Id.ThrowIfNotEqual(character.ZoneId ?? -1, ErrorCodes.InvalidZoneId); + _ = zone.Id.ThrowIfNotEqual(character.ZoneId ?? -1, ErrorCodes.InvalidZoneId); - var zonePosition = character.ZonePosition.ThrowIfNull(ErrorCodes.InvalidPosition); + Position? zonePosition = character.ZonePosition.ThrowIfNull(ErrorCodes.InvalidPosition); spawnPosition = (Position)zonePosition; } spawnPosition = zone.FixZ(spawnPosition); - var finder = new ClosestWalkablePositionFinder(zone, spawnPosition, player); - var validPosition = finder.FindOrThrow(); + ClosestWalkablePositionFinder finder = new ClosestWalkablePositionFinder(zone, spawnPosition, player); + Position validPosition = finder.FindOrThrow(); - var zoneStorage = zone.Configuration.GetStorage(); + ZoneStorage zoneStorage = zone.Configuration.GetStorage(); player.Parent = zoneStorage.Eid; player.FullCoreRecharge(); @@ -611,14 +581,14 @@ public static Player LoadPlayerAndAddToZone(IZone zone, Character character) [CanBeNull] public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, bool applyInvulnerable) { - var zone = Zone; + IZone zone = Zone; if (zone == null) { return null; } - var teleport = teleportStrategyFactories.TeleportWithinZoneFactory(); + TeleportWithinZone teleport = teleportStrategyFactories.TeleportWithinZoneFactory(); if (teleport == null) { @@ -629,14 +599,14 @@ public Task TeleportToPositionAsync(Position target, bool applyTeleportSickness, teleport.ApplyTeleportSickness = applyTeleportSickness; teleport.ApplyInvulnerable = applyInvulnerable; - var task = teleport.DoTeleportAsync(this); + Task task = teleport.DoTeleportAsync(this); return task?.LogExceptions(); } public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) { - var data = unit.BaseInfoToDictionary(); + Dictionary data = unit.BaseInfoToDictionary(); data.Add(k.timeOut, (int)timeout.TotalMilliseconds); data.Add(k.started, (long)start.TotalMilliseconds); @@ -650,7 +620,7 @@ public void SendStartProgressBar(Unit unit, TimeSpan timeout, TimeSpan start) public void SendEndProgressBar(Unit unit, bool success = true) { - var info = unit.BaseInfoToDictionary(); + Dictionary info = unit.BaseInfoToDictionary(); info.Add(k.success, success); Message.Builder.SetCommand(Commands.AlarmOver).WithData(info).ToCharacter(Character).Send(); @@ -658,7 +628,7 @@ public void SendEndProgressBar(Unit unit, bool success = true) public void SendArtifactRadarBeam(Position targetPosition) { - var builder = Beam.NewBuilder() + BeamBuilder builder = Beam.NewBuilder() .WithType(BeamType.artifact_radar) .WithSourcePosition(targetPosition) .WithTargetPosition(targetPosition) @@ -681,7 +651,7 @@ public void ReloadContainer() } else { - using (var scope = Db.CreateTransaction()) + using (TransactionScope scope = Db.CreateTransaction()) { Reload(); scope.Complete(); @@ -693,14 +663,14 @@ public void UpdateCorporationOnZone(long newCorporationEid) { CorporationEid = newCorporationEid; - var playersOnZone = Zone.GetCharacters().ToArray(); + Character[] playersOnZone = Zone.GetCharacters().ToArray(); if (playersOnZone.Length <= 0) { return; } - var result = new Dictionary + Dictionary result = new Dictionary { {k.corporationEID, newCorporationEid}, {k.characterID, Character.Id}, @@ -729,7 +699,7 @@ public void AddInCombatWith(Unit enemy) { SetCombatState(true); - var enemyPlayer = enemy as Player; + Player enemyPlayer = enemy as Player; enemyPlayer?.SetCombatState(true); } @@ -746,19 +716,20 @@ unit is IPBSObject || unit is WallHealer || unit is ProximityDeviceBase || (unit is BlobEmitterUnit b && b.IsPlayerSpawned) || - unit is RemoteControlledCreature; + (unit is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player); } protected override void OnDead(Unit killer) { - HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); + _ = HandlePlayerDeadAsync(Zone, killer).ContinueWith(t => base.OnDead(killer)); } protected override void OnTileChanged() { base.OnTileChanged(); - var zone = Zone; + IZone zone = Zone; if (zone == null) { @@ -767,7 +738,7 @@ protected override void OnTileChanged() MissionHandler?.MissionUpdateOnTileChange(); - var controlInfo = zone.Terrain.Controls.GetValue(CurrentPosition); + TerrainControlInfo controlInfo = zone.Terrain.Controls.GetValue(CurrentPosition); ApplyHighwayEffect(controlInfo.IsAnyHighway); @@ -783,16 +754,16 @@ protected override void OnCellChanged(CellCoord lastCellCoord, CellCoord current { base.OnCellChanged(lastCellCoord, currentCellCoord); - var zone = Zone; + IZone zone = Zone; if (zone == null) { return; } - Task.Run(() => + _ = Task.Run(() => { - var district = currentCellCoord.ComputeDistrict(lastCellCoord); + GridDistricts district = currentCellCoord.ComputeDistrict(lastCellCoord); zone.SendBeamsToPlayer(this, district); Session.SendTerrainData(); }).LogExceptions(); @@ -810,12 +781,7 @@ protected internal override void UpdatePlayerVisibility(Player player) protected override bool IsDetected(Unit target) { - if (Gang.IsMember(target)) - { - return true; - } - - return base.IsDetected(target); + return Gang.IsMember(target) || base.IsDetected(target); } protected override bool IsHostileFor(Unit unit) @@ -866,7 +832,7 @@ 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(); @@ -928,7 +894,7 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) MissionHandler.InitMissions(); Direction = FastRandom.NextDouble(); - var p = DynamicProperties.GetProperty(k.pvpRemaining); + IDynamicProperty p = DynamicProperties.GetProperty(k.pvpRemaining); if (!p.HasValue) { @@ -939,16 +905,6 @@ protected override void OnEnterZone(IZone zone, ZoneEnterType enterType) p.Clear(); } - protected override void OnBeforeRemovedFromZone(IZone zone) - { - var remoteController = this.Modules?.FirstOrDefault(x => x is RemoteControllerModule); - - if (remoteController != null) - { - (remoteController as RemoteControllerModule).CloseAllChannels(); - } - } - protected override void OnRemovedFromZone(IZone zone) { Session.SendPacket(ExitPacketBuilder); @@ -981,14 +937,14 @@ private void OnUnitUpdated(Unit unit, UnitUpdatedEventArgs e) return; } - var send = (e.UpdateTypes & UnitUpdateTypes.Visibility) > 0 || (e.UpdatedProperties != null && e.UpdatedProperties.Any(p => p.Field.IsPublic())); + bool send = (e.UpdateTypes & UnitUpdateTypes.Visibility) > 0 || (e.UpdatedProperties != null && e.UpdatedProperties.Any(p => p.Field.IsPublic())); if (!send) { return; } - var v = Visibility.Invisible; + Visibility v = Visibility.Invisible; if (unit.InZone) { @@ -1010,7 +966,7 @@ private void UpdateCombat(TimeSpan time) return; } - combatTimer.Update(time); + _ = combatTimer.Update(time); if (!combatTimer.Passed) { @@ -1022,7 +978,7 @@ private void UpdateCombat(TimeSpan time) private void Reload() { - var container = GetContainer(); + RobotInventory container = GetContainer(); Debug.Assert(container != null, "container != null"); container.EnlistTransaction(); @@ -1032,7 +988,7 @@ private void Reload() private CombatLogger CreateCombatLogger() { - var logger = combatLoggerFactory(this); + CombatLogger logger = combatLoggerFactory(this); logger.Expired = () => { @@ -1051,7 +1007,7 @@ private void ApplySyndicateAreaEffect(bool apply) { if (apply) { - var effectBuilder = NewEffectBuilder().SetType(EffectType.effect_syndicate_area); + EffectBuilder effectBuilder = NewEffectBuilder().SetType(EffectType.effect_syndicate_area); ApplyEffect(effectBuilder); } else @@ -1064,7 +1020,7 @@ private void ApplyHighwayEffect(bool apply) { if (apply) { - var effectBuilder = NewEffectBuilder() + EffectBuilder effectBuilder = NewEffectBuilder() .SetType(EffectType.effect_highway) .WithPropertyModifier(ItemPropertyModifier.Create(AggregateField.effect_speed_highway_modifier, 1.0)); @@ -1088,7 +1044,7 @@ private Task HandlePlayerDeadAsync(IZone zone, Unit killer) private void HandlePlayerDead(IZone zone, Unit killer) { - using (var scope = Db.CreateTransaction()) + using (TransactionScope scope = Db.CreateTransaction()) { EnlistTransaction(); @@ -1098,29 +1054,29 @@ private void HandlePlayerDead(IZone zone, Unit killer) SaveCombatLog(zone, killer); - var character = Character; - var dockingBase = character.GetHomeBaseOrCurrentBase(); + Character character = Character; + DockingBase dockingBase = character.GetHomeBaseOrCurrentBase(); dockingBase.DockIn(character, NormalUndockDelay, ZoneExitType.Died); PlayerDeathLogger.Log.Write(zone, this, killer); - var wasInsured = InsuranceHelper.CheckInsuranceOnDeath(Eid, Definition); + bool wasInsured = InsuranceHelper.CheckInsuranceOnDeath(Eid, Definition); if (!Session.AccessLevel.IsAdminOrGm()) { - var robotInventory = GetContainer(); + RobotInventory robotInventory = GetContainer(); Debug.Assert(robotInventory != null); - var lootItems = new List(); + List lootItems = new List(); - foreach (var module in Modules.Where(m => LootHelper.Roll())) + foreach (Module module in Modules.Where(m => LootHelper.Roll())) { lootItems.Add(LootItemBuilder.Create(module).AsDamaged().Build()); - var activeModule = module as ActiveModule; - var ammo = activeModule?.GetAmmo(); + ActiveModule activeModule = module as ActiveModule; + Items.Ammos.Ammo ammo = activeModule?.GetAmmo(); if (ammo != null && LootHelper.Roll()) { @@ -1132,11 +1088,9 @@ private void HandlePlayerDead(IZone zone, Unit killer) Repository.Delete(module); } - foreach (var item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) + foreach (Item item in robotInventory.GetItems(true).Where(i => i is VolumeWrapperContainer)) { - var wrapper = item as VolumeWrapperContainer; - - if (wrapper == null) + if (!(item is VolumeWrapperContainer wrapper)) { continue; } @@ -1146,11 +1100,11 @@ private void HandlePlayerDead(IZone zone, Unit killer) Repository.Delete(wrapper); } - foreach (var item in robotInventory + foreach (Item item in robotInventory .GetItems() .Where(i => LootHelper.Roll() && !i.ED.AttributeFlags.NonStackable)) { - var qtyMod = FastRandom.NextDouble(); + double qtyMod = FastRandom.NextDouble(); item.Quantity = (int)(item.Quantity * qtyMod); @@ -1184,26 +1138,26 @@ private void HandlePlayerDead(IZone zone, Unit killer) if (lootItems.Count > 0) { - var lootContainer = LootContainer.Create() + LootContainer lootContainer = LootContainer.Create() .AddLoot(lootItems) .BuildAndAddToZone(zone, CurrentPosition); if (lootContainer != null) { - var b = TransactionLogEvent.Builder() + TransactionLogEventBuilder b = TransactionLogEvent.Builder() .SetTransactionType(TransactionType.PutLoot) .SetCharacter(character) .SetContainer(lootContainer.Eid); - foreach (var lootItem in lootItems) + foreach (LootItem lootItem in lootItems) { - b.SetItem(lootItem.ItemInfo.Definition, lootItem.ItemInfo.Quantity); + _ = b.SetItem(lootItem.ItemInfo.Definition, lootItem.ItemInfo.Quantity); Character.LogTransaction(b); } } } - var killedByPlayer = (killer != null && killer.IsPlayer()); + bool killedByPlayer = killer != null && killer.IsPlayer(); Trashcan.Get() .MoveToTrash(this, Session.DisconnectTime, wasInsured, killedByPlayer, Session.InactiveTime); @@ -1221,7 +1175,7 @@ private void HandlePlayerDead(IZone zone, Unit killer) { Transaction.Current.OnCommited(() => { - var starterRobotInfo = new Dictionary + Dictionary starterRobotInfo = new Dictionary { {k.baseEID, Eid}, {k.robotEID, activeRobot.Eid} @@ -1238,12 +1192,12 @@ private void HandlePlayerDead(IZone zone, Unit killer) { this.Repair(); - var container = dockingBase.GetPublicContainer(); + PublicContainer container = dockingBase.GetPublicContainer(); container.AddItem(this, false); } - this.Save(); + Save(); scope.Complete(); } diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 92537b386..17c600161 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Perpetuum.Accounting.Characters; using Perpetuum.Builders; using Perpetuum.Containers; @@ -17,6 +14,9 @@ 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 { @@ -34,30 +34,15 @@ public abstract partial class Robot : Unit public bool IsSelected => RobotHelper.IsSelected(this); - public override double Health - { - get - { - if (IsRepackaged) - { - return base.Health; - } - - return ArmorMax > 0.0 && Armor > 0.0 + public override double Health => IsRepackaged + ? base.Health + : ArmorMax > 0.0 && Armor > 0.0 ? Armor.Ratio(ArmorMax) * 100 : base.Health; - } - } - public override double Mass - { - get { return RobotComponents.Sum(c => c.Mass); } - } + public override double Mass => RobotComponents.Sum(c => c.Mass); - public bool HasModule - { - get { return Modules.Any(); } - } + public bool HasModule => Modules.Any(); public bool IsItemsInContainer { @@ -68,51 +53,25 @@ public bool IsItemsInContainer return false; } - var robotInventory = GetContainer(); - - if (robotInventory == null) - { - return false; - } + RobotInventory robotInventory = GetContainer(); - return robotInventory.HasChildren; + return robotInventory != null && robotInventory.HasChildren; } } - public override bool IsStackable - { - get { return base.IsStackable && IsRepackaged; } - } + public override bool IsStackable => base.IsStackable && IsRepackaged; - public IEnumerable ExtensionBonusEnablerExtensions - { - get { return ExtensionBonuses.Select(cb => new Extension(cb.extensionId, 1)); } - } + public IEnumerable ExtensionBonusEnablerExtensions => ExtensionBonuses.Select(cb => new Extension(cb.extensionId, 1)); - protected IEnumerable ExtensionBonuses - { - get { return RobotComponents.SelectMany(component => component.ExtensionBonuses); } - } + protected IEnumerable ExtensionBonuses => RobotComponents.SelectMany(component => component.ExtensionBonuses); - public IEnumerable Modules - { - get { return _modules.Value; } - } + public IEnumerable Modules => _modules.Value; - public IEnumerable ActiveModules - { - get { return _activeModules.Value; } - } + public IEnumerable ActiveModules => _activeModules.Value; - public IEnumerable Components - { - get { return _components.Value; } - } + public IEnumerable Components => _components.Value; - public IEnumerable RobotComponents - { - get { return _robotComponents.Value; } - } + public IEnumerable RobotComponents => _robotComponents.Value; public override double Volume { @@ -123,7 +82,7 @@ public override double Volume return base.Volume; } - var volume = RobotComponents.Sum(c => c.Volume); + double volume = RobotComponents.Sum(c => c.Volume); volume *= Quantity; @@ -131,10 +90,7 @@ public override double Volume } } - public bool IsTrashed - { - get { return Trashcan.IsItemTrashed(this); } - } + public bool IsTrashed => Trashcan.IsItemTrashed(this); protected Robot() { @@ -158,7 +114,7 @@ public override void AcceptVisitor(IEntityVisitor visitor) public void VisitModules(IEntityVisitor visitor) { - foreach (var module in Modules) + foreach (Module module in Modules) { module.AcceptVisitor(visitor); } @@ -166,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); } @@ -174,14 +130,14 @@ 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(); + IDictionary info = base.GetDebugInfo(); info.Add("locksCount", _lockHandler.Count); @@ -196,8 +152,8 @@ public void FullArmorRepair() public void FullCoreRecharge() { - var currentCore = DynamicProperties.GetOrAdd(k.currentCore); - var coreMaxValue = CoreMax; + double currentCore = DynamicProperties.GetOrAdd(k.currentCore); + double coreMaxValue = CoreMax; if (currentCore >= coreMaxValue) { @@ -216,7 +172,7 @@ public RobotInventory GetContainer() public void StopAllModules() { - foreach (var module in ActiveModules) + foreach (ActiveModule module in ActiveModules) { module.State.SwitchTo(ModuleStateType.Idle); } @@ -230,7 +186,7 @@ public override void OnDeleteFromDb() public void EmptyRobot(Character character, Container targetContainer, bool withContainer = true) { - foreach (var module in Modules) + foreach (Module module in Modules) { module.Unequip(targetContainer); } @@ -240,21 +196,21 @@ public void EmptyRobot(Character character, Container targetContainer, bool with return; } - var container = GetContainer(); + RobotInventory container = GetContainer(); 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) { @@ -325,7 +281,7 @@ public void CheckEnergySystemAndThrowIfFailed() public void CreateComponents() { - foreach (var component in Template.BuildComponents()) + foreach (Item component in Template.BuildComponents()) { AddChild(component); } @@ -345,15 +301,14 @@ protected virtual void OnLockStateChanged(Lock @lock) { States.LockSomething = _lockHandler.Count > 0; - var unitLock = @lock as UnitLock; - if (unitLock != null) + if (@lock is UnitLock unitLock) { UpdateTypes |= UnitUpdateTypes.Lock; UpdateVisibilityOf(unitLock.Target); } - var builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); + AnonymousBuilder builder = new AnonymousBuilder(() => LockPacketBuilder.BuildPacket(@lock)); OnBroadcastPacket(builder.ToProxy()); } @@ -364,7 +319,7 @@ protected override void OnUpdate(TimeSpan time) _lockHandler.Update(time); - foreach (var robotComponent in RobotComponents) + foreach (RobotComponent robotComponent in RobotComponents) { robotComponent.Update(time); } @@ -379,12 +334,12 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) { base.OnDamageTaken(source, e); - var decayChance = this.decayChance.Value; - var random = FastRandom.NextDouble(); + double decayChance = this.decayChance.Value; + double random = FastRandom.NextDouble(); if (decayChance >= random) { - var d = Decay; + int d = Decay; if (d > 0) { @@ -396,16 +351,18 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) protected override bool IsDetected(Unit target) { - if (_lockHandler.IsLocked(target)) - { - return true; - } - - return base.IsDetected(target); + return _lockHandler.IsLocked(target) || base.IsDetected(target); } protected override void OnRemovedFromZone(IZone zone) { + Module remoteController = Modules?.FirstOrDefault(x => x is RemoteControllerModule); + + if (remoteController != null) + { + (remoteController as RemoteControllerModule).CloseAllChannels(); + } + base.OnRemovedFromZone(zone); } diff --git a/src/Perpetuum/Zones/DistanceConstants.cs b/src/Perpetuum/Zones/DistanceConstants.cs index fa743d32c..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,7 +19,7 @@ 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; @@ -35,7 +35,7 @@ public class DistanceConstants 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; @@ -57,17 +57,21 @@ public class DistanceConstants 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/GangEffect.cs b/src/Perpetuum/Zones/Effects/GangEffect.cs index 0ae6cd43c..c49458590 100644 --- a/src/Perpetuum/Zones/Effects/GangEffect.cs +++ b/src/Perpetuum/Zones/Effects/GangEffect.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; -using System.Linq; 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 { @@ -16,10 +16,11 @@ protected override void OnTick() { if (Owner != Source) { - var gang = Owner is Player player + Gang gang = Owner is Player player ? player.Gang - : Owner is RemoteControlledCreature turret - ? turret.Player.Gang + : Owner is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer.Gang : null; if (gang == null || !gang.IsMember((Player)Source)) @@ -36,10 +37,10 @@ protected override void OnTick() protected override IEnumerable GetTargets(IZone zone) { - var player = (Player)Owner; - var gangMembers = zone.GetGangMembers(player.Gang); - var alliedTurrets = zone.GetAlliedTurretsByPlayers(new[] { player }.Union(gangMembers)); - var alliedUnits = (gangMembers).Union(alliedTurrets); + 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/LandMines/LandMine.cs b/src/Perpetuum/Zones/LandMines/LandMine.cs index a90ee51d7..7c69bcfcb 100644 --- a/src/Perpetuum/Zones/LandMines/LandMine.cs +++ b/src/Perpetuum/Zones/LandMines/LandMine.cs @@ -1,10 +1,6 @@ -using Perpetuum.Accounting.Characters; -using Perpetuum.EntityFramework; -using Perpetuum.ExportedTypes; -using Perpetuum.Log; +using Perpetuum.ExportedTypes; using Perpetuum.Modules.Weapons; using Perpetuum.Players; -using Perpetuum.Robots; using Perpetuum.Timers; using Perpetuum.Units; using Perpetuum.Units.DockingBases; @@ -14,7 +10,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Policy; namespace Perpetuum.Zones.LandMines { @@ -23,19 +18,13 @@ public class LandMine : ProximityDeviceBase private const int BeamDistance = 600; private readonly IntervalTimer probingInterval = new IntervalTimer(TimeSpan.FromSeconds(2)); - public int TriggerMass - { - get - { - return ED.Options.GetOption("triggerMass"); - } - } + public int TriggerMass => ED.Options.GetOption("triggerMass"); protected override void OnUpdate(TimeSpan time) { - var despawnHelper = GetDespawnHelper(); + UnitDespawnHelper despawnHelper = GetDespawnHelper(); - probingInterval.Update(time); + _ = probingInterval.Update(time); if (!probingInterval.Passed) { @@ -47,9 +36,9 @@ protected override void OnUpdate(TimeSpan time) if (IsActive) { //detect - var robotsNearMe = GetNoticedUnits(); + List robotsNearMe = GetNoticedUnits(); - if (robotsNearMe.Exists(x => x.ActualMass > this.TriggerMass)) + if (robotsNearMe.Exists(x => x.ActualMass > TriggerMass)) { robotsNearMe[0].Zone.CreateBeam( BeamType.plant_bomb_explosion,//.timebomb_explosion, @@ -59,7 +48,7 @@ protected override void OnUpdate(TimeSpan time) .WithVisibility(BeamDistance)); // There be explosion - var damageBuilder = DamageInfo.Builder.WithAttacker(this) + 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) @@ -77,8 +66,8 @@ protected override void OnUpdate(TimeSpan time) if (despawnHelper == null) { - var m = GetPropertyModifier(AggregateField.despawn_time); - var timespan = TimeSpan.FromMilliseconds((int)m.Value); + Items.ItemPropertyModifier m = GetPropertyModifier(AggregateField.despawn_time); + TimeSpan timespan = TimeSpan.FromMilliseconds((int)m.Value); SetDespawnTime(timespan); despawnHelper = GetDespawnHelper(); } @@ -96,7 +85,7 @@ public override List GetNoticedUnits() } protected override bool IsDetected(Unit target) { - var range = ED.Config.item_work_range ?? 5.0; + double range = ED.Config.item_work_range ?? 5.0; return IsInRangeOf3D(target, range); } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index 0dd3f0c3d..01f85849e 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -39,9 +39,10 @@ public void Visit(Player player) } if (smartCreature is RemoteControlledCreature remoteControlledCreature && + (remoteControlledCreature.CommandRobot is Player) && player.Zone.Configuration.IsAlpha && !player.HasPvpEffect && - !remoteControlledCreature.Player.HasPvpEffect) + !remoteControlledCreature.CommandRobot.HasPvpEffect) { return; } diff --git a/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs index 1ac7538e2..d173755ea 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/ModuleActivator.cs @@ -1,7 +1,7 @@ using Perpetuum.EntityFramework; +using Perpetuum.Modules; using Perpetuum.Modules.EffectModules; using Perpetuum.Modules.Weapons; -using Perpetuum.Modules; using Perpetuum.Timers; using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.Terrains; @@ -26,7 +26,8 @@ public class ModuleActivator : IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private const double ENERGY_INJECTOR_THRESHOLD = 0.65; private const double ARMOR_REPAIR_THRESHOLD = 0.95; @@ -52,7 +53,7 @@ public ModuleActivator(ActiveModule module) public void Update(TimeSpan time) { - _timer.Update(time); + _ = _timer.Update(time); if (!_timer.Passed) { @@ -71,56 +72,56 @@ public void Update(TimeSpan time) public void Visit(MissileWeaponModule module) { - var hasShieldEffect = module.ParentRobot.HasShieldEffect; + bool hasShieldEffect = module.ParentRobot.HasShieldEffect; if (hasShieldEffect) { return; } - var primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); + UnitLock primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); if (primaryLock == null) { return; } - var visibility = module.ParentRobot.GetVisibility(primaryLock.Target); + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(primaryLock.Target); if (visibility == null) { return; } - var result = visibility.GetLineOfSight(true); + LOSResult result = visibility.GetLineOfSight(true); TryActiveModule(result, primaryLock); } public void Visit(WeaponModule module) { - var hasShieldEffect = module.ParentRobot.HasShieldEffect; + bool hasShieldEffect = module.ParentRobot.HasShieldEffect; if (hasShieldEffect) { return; } - var primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); + UnitLock primaryLock = module.ParentRobot.GetFinishedPrimaryLock(); if (primaryLock == null) { return; } - var visibility = module.ParentRobot.GetVisibility(primaryLock.Target); + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(primaryLock.Target); if (visibility == null) { return; } - var result = visibility.GetLineOfSight(false); + LOSResult result = visibility.GetLineOfSight(false); TryActiveModule(result, primaryLock); } @@ -157,7 +158,7 @@ public void Visit(SensorJammerModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { @@ -175,7 +176,7 @@ public void Visit(SensorDampenerModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { @@ -193,7 +194,7 @@ public void Visit(WebberModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { @@ -211,7 +212,7 @@ public void Visit(TargetPainterModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { @@ -234,7 +235,7 @@ public void Visit(BlobEmissionModulatorModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { @@ -252,7 +253,7 @@ public void Visit(TargetBlinderModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { @@ -270,21 +271,21 @@ public void Visit(EnergyNeutralizerModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { return; } - var visibility = module.ParentRobot.GetVisibility(lockTarget.Target); + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(lockTarget.Target); if (visibility == null) { return; } - var r = visibility.GetLineOfSight(false); + LOSResult r = visibility.GetLineOfSight(false); if (r != null && r.hit) { @@ -302,21 +303,21 @@ public void Visit(EnergyVampireModule module) return; } - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); + UnitLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockTargetFor(module); if (lockTarget == null) { return; } - var visibility = module.ParentRobot.GetVisibility(lockTarget.Target); + Units.IUnitVisibility visibility = module.ParentRobot.GetVisibility(lockTarget.Target); if (visibility == null) { return; } - var r = visibility.GetLineOfSight(false); + LOSResult r = visibility.GetLineOfSight(false); if (r != null && r.hit) { @@ -355,7 +356,7 @@ public void Visit(CoreBoosterModule module) public void Visit(RemoteControlledDrillerModule module) { - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + TerrainLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); if (lockTarget == null) { @@ -368,7 +369,7 @@ public void Visit(RemoteControlledDrillerModule module) public void Visit(RemoteControlledHarvesterModule module) { - var lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); + TerrainLock lockTarget = ((Creature)module.ParentRobot).SelectOptimalLockIndustrialTargetFor(module); if (lockTarget == null) { @@ -379,6 +380,25 @@ public void Visit(RemoteControlledHarvesterModule module) 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)) diff --git a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs index bca0f1365..eca6ccc23 100644 --- a/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs +++ b/src/Perpetuum/Zones/NpcSystem/TargettingStrategies/CombatStrategies.cs @@ -40,7 +40,7 @@ public static bool TryInvokeStrategy(CombatPrimaryLockSelectionStrategy strategy private static bool TargetPropagatedPrimary(SmartCreature smartCreature, UnitLock[] locks) { - var propagatedPrimary = locks.FirstOrDefault(x => x.Target == ((smartCreature as RemoteControlledCreature).Player.GetPrimaryLock() as UnitLock)?.Target); + var propagatedPrimary = locks.FirstOrDefault(x => x.Target == ((smartCreature as RemoteControlledCreature).CommandRobot.GetPrimaryLock() as UnitLock)?.Target); if (propagatedPrimary == null) { diff --git a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs index 75d806df6..54a84988b 100644 --- a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs +++ b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs @@ -19,10 +19,7 @@ public CombatDrone(IStandingHandler standingHandler) { } - public bool IsInGuardRange - { - get { return CurrentPosition.IsInRangeOf2D(HomePosition, GuardRange); } - } + public bool IsInGuardRange => CurrentPosition.IsInRangeOf2D(HomePosition, GuardRange); public override void AcceptVisitor(IEntityVisitor visitor) { @@ -39,7 +36,7 @@ public override bool IsHostile(Player player) public override void OnAggression(Unit victim) { - this.Player.OnAggression(victim); + CommandRobot.OnAggression(victim); } protected override bool IsHostileFor(Unit unit) @@ -49,18 +46,13 @@ protected override bool IsHostileFor(Unit unit) protected override void OnUpdate(TimeSpan time) { - HomePosition = Player.CurrentPosition; + HomePosition = CommandRobot.CurrentPosition; base.OnUpdate(time); } protected override bool IsDetected(Unit target) { - if (!IsCommandBotPrimaryLock(target)) - { - return false; - } - - return base.IsDetected(target); + return IsCommandBotPrimaryLock(target) && base.IsDetected(target); } internal override bool IsHostile(Npc npc) @@ -70,12 +62,12 @@ internal override bool IsHostile(Npc npc) internal override bool IsHostile(CombatDrone drone) { - return IsHostilePlayer(drone.Player); + return !(drone.CommandRobot is Player player) || IsHostilePlayer(player); } internal override bool IsHostile(SentryTurret turret) { - return IsHostilePlayer(turret.Player); + return !(turret.CommandRobot is Player player) || IsHostilePlayer(player); } protected override void OnUnitLockStateChanged(Lock @lock) @@ -94,17 +86,12 @@ protected override void UpdateUnitVisibility(Unit target) private bool IsCommandBotPrimaryLock(Unit unit) { - var primaryLock = this.Player.GetPrimaryLock(); + Lock primaryLock = CommandRobot.GetPrimaryLock(); - if (primaryLock != null && + return primaryLock != null && primaryLock.State == Locking.LockState.Locked && primaryLock is UnitLock && - (primaryLock as UnitLock).Target == unit) - { - return true; - } - - return false; + (primaryLock as UnitLock).Target == unit; } } } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 8259b4cde..4e055850b 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -1,4 +1,5 @@ using Perpetuum.Players; +using Perpetuum.Robots; using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.NpcSystem; @@ -16,7 +17,7 @@ public class RemoteControlledCreature : SmartCreature public event RemoteChannelEventHandler RemoteChannelDeactivated; - public Player Player { get; private set; } + public Robot CommandRobot { get; private set; } public double RemoteChannelBandwidthUsage { get; private set; } @@ -34,9 +35,9 @@ public RemoteControlledCreature(IStandingHandler standingHandler) this.standingHandler = standingHandler; } - public void SetPlayer(Player player) + public void SetCommandRobot(Robot commandRobot) { - Player = player; + CommandRobot = commandRobot; } public void SetBandwidthUsage(double value) @@ -46,7 +47,7 @@ public void SetBandwidthUsage(double value) public override void AddThreat(Unit hostile, Threat threat, bool spreadToGroup) { - if (hostile.IsPlayer() && Player == (hostile as Player)) + if (hostile.IsPlayer() && CommandRobot == (hostile as Player)) { return; } @@ -67,27 +68,32 @@ protected override void OnBeforeRemovedFromZone(IZone zone) protected bool IsHostilePlayer(Player targetPlayer) { - if (Player == targetPlayer) + if (!(CommandRobot is Player player)) + { + return true; + } + + if (player == targetPlayer) { return false; } - if (Zone.Configuration.IsAlpha && !Player.HasPvpEffect && !targetPlayer.HasPvpEffect) + if (Zone.Configuration.IsAlpha && !player.HasPvpEffect && !targetPlayer.HasPvpEffect) { return false; } - if (Player != null && Player == targetPlayer) + if (player == targetPlayer) { return false; } - if (Player.Gang != null && Player.Gang.IsMember(targetPlayer.Character)) + if (player.Gang != null && player.Gang.IsMember(targetPlayer.Character)) { return false; } - double corporationStanding = standingHandler.GetStanding(Player.CorporationEid, targetPlayer.CorporationEid); + double corporationStanding = standingHandler.GetStanding(player.CorporationEid, targetPlayer.CorporationEid); if (corporationStanding > StandingLimit) { diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 3592c2b1d..bd7032012 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -33,7 +33,7 @@ public override bool IsHostile(Player targetPlayer) public override void OnAggression(Unit victim) { - this.Player.OnAggression(victim); + CommandRobot.OnAggression(victim); } internal override bool IsHostile(Npc npc) @@ -43,12 +43,12 @@ internal override bool IsHostile(Npc npc) internal override bool IsHostile(CombatDrone drone) { - return IsHostilePlayer(drone.Player); + return !(drone.CommandRobot is Player player) || IsHostilePlayer(player); } internal override bool IsHostile(SentryTurret turret) { - return IsHostilePlayer(turret.Player); + return !(turret.CommandRobot is Player player) || IsHostilePlayer(player); } protected override void UpdateUnitVisibility(Unit target) diff --git a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs index 05963940c..d21254f7a 100644 --- a/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs +++ b/src/Perpetuum/Zones/Scanning/Modules/ItemScannerModule.cs @@ -21,8 +21,9 @@ protected override void HandleOffensivePVPCheck(Player parentPlayer, UnitLock un { Player targetPlayer = unitLockTarget.Target is Player player ? player - : unitLockTarget.Target is RemoteControlledCreature remoteCreature - ? remoteCreature.Player + : unitLockTarget.Target is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player ownerPlayer + ? ownerPlayer : null; if (targetPlayer == null) @@ -63,6 +64,7 @@ private double GetProbability(Unit target) { double probability = 1.0; probability = ModifyValueByOptimalRange(target, probability); + return probability; } } diff --git a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs index eb75bd2f6..a1a039815 100644 --- a/src/Perpetuum/Zones/ZoneExtensions.Gang.cs +++ b/src/Perpetuum/Zones/ZoneExtensions.Gang.cs @@ -42,7 +42,7 @@ public static IEnumerable GetAlliedTurretsByPlayers(th .Where(x => x is RemoteControlledCreature) .Join( players, - turret => ((RemoteControlledCreature)turret).Player, + turret => ((RemoteControlledCreature)turret).CommandRobot, player => player, (turret, player) => (RemoteControlledCreature)turret); } From 508e12ec52d831f62ec270c0fab7b9237e5e37d9 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Tue, 13 Feb 2024 11:19:19 +0500 Subject: [PATCH 448/457] - Industrial turrets can now be placed anywhere - When RCU turrets are out of operational range, they will explode - Landmines now have 15 sec grace period when deployed - Fixed an issue with RCU being left on field when teleporting using anomalies --- src/Perpetuum.ExportedTypes/AttributeFlags.cs | 134 +++++++++--------- .../Modules/RemoteControllerModule.cs | 30 ++-- src/Perpetuum/Robots/Robot.cs | 4 +- src/Perpetuum/Zones/LandMines/LandMine.cs | 12 +- .../Zones/RemoteControl/IndustrialTurret.cs | 31 ++-- .../RemoteControl/RemoteControlledCreature.cs | 2 + .../Zones/RemoteControl/SentryTurret.cs | 11 ++ 7 files changed, 128 insertions(+), 96 deletions(-) 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/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index f11658afb..689c37584 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -116,26 +116,12 @@ protected override void OnAction() return; } - Position spawnPosition = Zone.FixZ(targetPosition); - Zone.Units .OfType() - .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_RCU) + .WithinRange(Zone.FixZ(targetPosition), DistanceConstants.RCU_DEPLOY_RANGE_FROM_RCU) .Any() .ThrowIfTrue(ErrorCodes.RemoteControlledTurretInRange); - Zone.Units - .OfType() - .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_BASE) - .Any() - .ThrowIfTrue(ErrorCodes.NotDeployableNearObject); - - Zone.Units - .OfType() - .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_TELEPORT) - .Any() - .ThrowIfTrue(ErrorCodes.TeleportIsInRange); - LOSResult r = Zone.IsInLineOfSight(ParentRobot, targetPosition.AddToZ(SentryTurretHeight), false); if (r.hit) @@ -165,6 +151,20 @@ protected override void OnAction() { 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.NotDeployableNearObject); + + Zone.Units + .OfType() + .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_TELEPORT) + .Any() + .ThrowIfTrue(ErrorCodes.TeleportIsInRange); } else if (ammo.ED.Options.TurretType == TurretType.Mining || ammo.ED.Options.TurretType == TurretType.Harvesting) { diff --git a/src/Perpetuum/Robots/Robot.cs b/src/Perpetuum/Robots/Robot.cs index 17c600161..7d836390d 100644 --- a/src/Perpetuum/Robots/Robot.cs +++ b/src/Perpetuum/Robots/Robot.cs @@ -354,7 +354,7 @@ protected override bool IsDetected(Unit target) return _lockHandler.IsLocked(target) || base.IsDetected(target); } - protected override void OnRemovedFromZone(IZone zone) + protected override void OnBeforeRemovedFromZone(IZone zone) { Module remoteController = Modules?.FirstOrDefault(x => x is RemoteControllerModule); @@ -363,7 +363,7 @@ protected override void OnRemovedFromZone(IZone zone) (remoteController as RemoteControllerModule).CloseAllChannels(); } - base.OnRemovedFromZone(zone); + base.OnBeforeRemovedFromZone(zone); } private void InitComponents() diff --git a/src/Perpetuum/Zones/LandMines/LandMine.cs b/src/Perpetuum/Zones/LandMines/LandMine.cs index 7c69bcfcb..a3f3e8ec2 100644 --- a/src/Perpetuum/Zones/LandMines/LandMine.cs +++ b/src/Perpetuum/Zones/LandMines/LandMine.cs @@ -17,11 +17,21 @@ public class LandMine : ProximityDeviceBase { private const int BeamDistance = 600; private readonly IntervalTimer probingInterval = new IntervalTimer(TimeSpan.FromSeconds(2)); + 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; + } + + _ = gracePeriodInterval.Update(time); + UnitDespawnHelper despawnHelper = GetDespawnHelper(); _ = probingInterval.Update(time); @@ -77,12 +87,12 @@ protected override void OnUpdate(TimeSpan time) #region probe functions - // egy adott pillanatban kiket lat [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; diff --git a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs index 990c9a665..04680ac6a 100644 --- a/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/IndustrialTurret.cs @@ -1,16 +1,15 @@ -using Perpetuum.Containers; -using Perpetuum.Data; +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; -using System.Security.Policy; -using Perpetuum.Services.Standing; +using System.Transactions; namespace Perpetuum.Zones.RemoteControl { @@ -51,17 +50,17 @@ protected override void OnBeforeRemovedFromZone(IZone zone) public void EjectCargo(IZone zone) { - using (var scope = Db.CreateTransaction()) + using (TransactionScope scope = Db.CreateTransaction()) { try { - var robotInventory = GetContainer(); + Robots.RobotInventory robotInventory = GetContainer(); Debug.Assert(robotInventory != null); - var lootItems = new List(); + List lootItems = new List(); - foreach (var item in robotInventory + foreach (Items.Item item in robotInventory .GetItems() .Where(i => !i.ED.AttributeFlags.NonStackable)) { @@ -81,12 +80,12 @@ public void EjectCargo(IZone zone) if (lootItems.Count > 0) { - var lootContainer = LootContainer.Create() + _ = LootContainer.Create() .AddLoot(lootItems) .BuildAndAddToZone(zone, CurrentPosition); } - this.Save(); + Save(); scope.Complete(); } @@ -96,5 +95,15 @@ public void EjectCargo(IZone zone) } } } + + protected override void OnUpdate(TimeSpan time) + { + if (!IsInOperationalRange) + { + Kill(); + } + + base.OnUpdate(time); + } } } diff --git a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs index 4e055850b..c7f7a980a 100644 --- a/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs +++ b/src/Perpetuum/Zones/RemoteControl/RemoteControlledCreature.cs @@ -26,6 +26,8 @@ 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; diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index bd7032012..d67e382c8 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -3,6 +3,7 @@ using Perpetuum.Services.Standing; using Perpetuum.Units; using Perpetuum.Zones.NpcSystem; +using System; namespace Perpetuum.Zones.RemoteControl { @@ -59,5 +60,15 @@ protected override void UpdateUnitVisibility(Unit target) UpdateVisibility(target); } } + + protected override void OnUpdate(TimeSpan time) + { + if (!IsInOperationalRange) + { + Kill(); + } + + base.OnUpdate(time); + } } } \ No newline at end of file From ce2610aebb85c8b056503cbfc744ce518572b2cc Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Wed, 14 Feb 2024 07:47:02 +0300 Subject: [PATCH 449/457] Blind terminal chat - set JoinChannel to protected; - join character to the terminal chat via TryJoinChannel. --- .../Characters/CharacterSelect.cs | 2 +- .../Services/Channels/ChannelManager.cs | 5 +++++ .../Units/DockingBases/DockingBase.cs | 20 +++++++++++++++++-- .../DockingBases/ExpiringPBSDockingBase.cs | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) 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/Services/Channels/ChannelManager.cs b/src/Perpetuum/Services/Channels/ChannelManager.cs index fb4b17617..5b349a017 100644 --- a/src/Perpetuum/Services/Channels/ChannelManager.cs +++ b/src/Perpetuum/Services/Channels/ChannelManager.cs @@ -54,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) diff --git a/src/Perpetuum/Units/DockingBases/DockingBase.cs b/src/Perpetuum/Units/DockingBases/DockingBase.cs index 8a340a5a0..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 virtual 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 || diff --git a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs index 4d46802a9..8adb00bc9 100644 --- a/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs +++ b/src/Perpetuum/Zones/PBS/DockingBases/ExpiringPBSDockingBase.cs @@ -106,7 +106,7 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); } - public override void JoinChannel(Character character) + protected override void JoinChannel(Character character) { base.JoinChannel(character); ChannelManager.Announcement(ChannelName, _announcer, $"Base is going to expire in: {Remaining.ToHumanTimeString()}"); From 40db598e78025eb4b0dfeb1ff64f38eb35f65d42 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sat, 17 Feb 2024 16:37:35 +0500 Subject: [PATCH 450/457] - Minor changes related to CT and Tellesis effects --- src/Perpetuum.ExportedTypes/CategoryFlags.cs | 1 + src/Perpetuum.ExportedTypes/EffectType.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Perpetuum.ExportedTypes/CategoryFlags.cs b/src/Perpetuum.ExportedTypes/CategoryFlags.cs index b02e25500..3e5954442 100644 --- a/src/Perpetuum.ExportedTypes/CategoryFlags.cs +++ b/src/Perpetuum.ExportedTypes/CategoryFlags.cs @@ -377,6 +377,7 @@ public enum CategoryFlags : long 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, diff --git a/src/Perpetuum.ExportedTypes/EffectType.cs b/src/Perpetuum.ExportedTypes/EffectType.cs index 2847d19f1..54e22e25b 100644 --- a/src/Perpetuum.ExportedTypes/EffectType.cs +++ b/src/Perpetuum.ExportedTypes/EffectType.cs @@ -122,5 +122,6 @@ public enum EffectType effect_gamma_t3 = 119, effect_gamma_tc = 120, effect_mine_detector = 121, + effect_tellesis = 122, } } From 5b06f811e94e17355097ba97a13d9444fc8c488c Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Tue, 27 Feb 2024 01:46:10 +0500 Subject: [PATCH 451/457] - Fixed landmines won't despawn --- src/Perpetuum/Units/UnitDespawnHelper.cs | 33 ++++--- src/Perpetuum/Zones/LandMines/LandMine.cs | 88 +++++++----------- .../Zones/ProximityProbes/ProximityDevice.cs | 93 ++++++++++--------- .../Zones/ProximityProbes/ProximityProbe.cs | 23 +++-- 4 files changed, 119 insertions(+), 118 deletions(-) diff --git a/src/Perpetuum/Units/UnitDespawnHelper.cs b/src/Perpetuum/Units/UnitDespawnHelper.cs index b0656902f..43ea641bd 100644 --- a/src/Perpetuum/Units/UnitDespawnHelper.cs +++ b/src/Perpetuum/Units/UnitDespawnHelper.cs @@ -1,7 +1,7 @@ -using System; using Perpetuum.ExportedTypes; using Perpetuum.Timers; using Perpetuum.Zones.Effects; +using System; namespace Perpetuum.Units { @@ -21,10 +21,7 @@ public class UnitDespawnHelper : IUnitDespawnHelper public UnitDespawnerCanApplyEffect CanApplyDespawnEffect { protected get; set; } public UnitDespawnStrategy DespawnStrategy { protected get; set; } - public virtual EffectType DespawnEffect - { - get { return EffectType.effect_despawn_timer; } - } + public virtual EffectType DespawnEffect => EffectType.effect_despawn_timer; protected readonly TimeSpan _despawnTime; protected readonly EffectToken _effectToken = EffectToken.NewToken(); @@ -47,7 +44,9 @@ public virtual void Update(TimeSpan time, Unit unit) TryReApplyDespawnEffect(unit); if (HasEffectOrPending(unit)) + { return; + } CanApplyDespawnEffect = null; if (DespawnStrategy != null) @@ -64,20 +63,24 @@ public virtual void Update(TimeSpan time, Unit unit) 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); } protected void ApplyDespawnEffect(Unit unit) { - var effectBuilder = unit.NewEffectBuilder().SetType(DespawnEffect).WithDuration(_despawnTime).WithToken(_effectToken); + EffectBuilder effectBuilder = unit.NewEffectBuilder().SetType(DespawnEffect).WithDuration(_despawnTime).WithToken(_effectToken); unit.ApplyEffect(effectBuilder); } @@ -88,18 +91,16 @@ public override string ToString() 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 - { - get { return EffectType.effect_stronghold_despawn_timer; } - } + public override EffectType DespawnEffect => EffectType.effect_stronghold_despawn_timer; private CancellableDespawnHelper(TimeSpan despawnTime) : base(despawnTime) { } private bool _canceled = false; @@ -119,15 +120,17 @@ public override void Update(TimeSpan time, Unit unit) _timer.Update(time).IsPassed(() => { if (_canceled || HasEffectOrPending(unit)) + { return; + } DespawnStrategy?.Invoke(unit); }); } - public new static CancellableDespawnHelper Create(Unit unit, TimeSpan despawnTime) + public static new CancellableDespawnHelper Create(Unit unit, TimeSpan despawnTime) { - var helper = new CancellableDespawnHelper(despawnTime); + CancellableDespawnHelper helper = new CancellableDespawnHelper(despawnTime); helper.ApplyDespawnEffect(unit); return helper; } diff --git a/src/Perpetuum/Zones/LandMines/LandMine.cs b/src/Perpetuum/Zones/LandMines/LandMine.cs index a3f3e8ec2..dbaacada1 100644 --- a/src/Perpetuum/Zones/LandMines/LandMine.cs +++ b/src/Perpetuum/Zones/LandMines/LandMine.cs @@ -1,4 +1,5 @@ -using Perpetuum.ExportedTypes; +using Perpetuum.EntityFramework; +using Perpetuum.ExportedTypes; using Perpetuum.Modules.Weapons; using Perpetuum.Players; using Perpetuum.Timers; @@ -16,7 +17,6 @@ namespace Perpetuum.Zones.LandMines public class LandMine : ProximityDeviceBase { private const int BeamDistance = 600; - private readonly IntervalTimer probingInterval = new IntervalTimer(TimeSpan.FromSeconds(2)); private readonly IntervalTimer gracePeriodInterval = new IntervalTimer(TimeSpan.FromSeconds(15)); public int TriggerMass => ED.Options.GetOption("triggerMass"); @@ -30,59 +30,35 @@ protected override void OnUpdate(TimeSpan time) return; } - _ = gracePeriodInterval.Update(time); - - UnitDespawnHelper despawnHelper = GetDespawnHelper(); - - _ = probingInterval.Update(time); - - if (!probingInterval.Passed) - { - return; - } - - probingInterval.Reset(); - - if (IsActive) - { - //detect - List robotsNearMe = GetNoticedUnits(); - - if (robotsNearMe.Exists(x => x.ActualMass > TriggerMass)) - { - robotsNearMe[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); - - robotsNearMe[0].Zone.DoAoeDamageAsync(damageBuilder); - - OnDead(robotsNearMe[0]); - } - } + base.OnUpdate(time); + } - if (despawnHelper == null) + public override void OnUnitsFound(List unitsFound) + { + if (unitsFound.Exists(x => x.ActualMass > TriggerMass)) { - Items.ItemPropertyModifier m = GetPropertyModifier(AggregateField.despawn_time); - TimeSpan timespan = TimeSpan.FromMilliseconds((int)m.Value); - SetDespawnTime(timespan); - despawnHelper = GetDespawnHelper(); + 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]); } - - despawnHelper.Update(time, this); } #region probe functions @@ -107,6 +83,14 @@ public override void CheckDeploymentAndThrow(IZone zone, Position spawnPosition) 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/ProximityProbes/ProximityDevice.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs index bfe311697..fbf8db883 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityDevice.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Perpetuum.Accounting.Characters; +using Perpetuum.Accounting.Characters; using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Groups.Corporations; @@ -12,6 +9,9 @@ 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 { @@ -23,7 +23,7 @@ public interface ICharactersRegistered } //ez van kinn a terepen - public abstract class ProximityDeviceBase : Unit , ICharactersRegistered + public abstract class ProximityDeviceBase : Unit, ICharactersRegistered { private readonly CharactersRegisterHelper _charactersRegisterHelper; private IntervalTimer _probingInterval = new IntervalTimer(TimeSpan.FromSeconds(10)); @@ -52,7 +52,7 @@ public void SetDespawnTime(TimeSpan despawnTime) // TODO: have to find better solution public UnitDespawnHelper GetDespawnHelper() { - return this._despawnHelper; + return _despawnHelper; } protected internal override void UpdatePlayerVisibility(Player player) @@ -63,24 +63,28 @@ protected internal override void UpdatePlayerVisibility(Player 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); + _ = _probingInterval.Update(time); if (!_probingInterval.Passed) + { return; + } _probingInterval.Reset(); if (IsActive) { //detect - var robotsNearMe = GetNoticedUnits(); + List robotsNearMe = GetNoticedUnits(); //do something OnUnitsFound(robotsNearMe); @@ -88,8 +92,8 @@ protected override void OnUpdate(TimeSpan time) if (_despawnHelper == null) { - var m = GetPropertyModifier(AggregateField.despawn_time); - var timespan = TimeSpan.FromMilliseconds((int)m.Value); + Items.ItemPropertyModifier m = GetPropertyModifier(AggregateField.despawn_time); + TimeSpan timespan = TimeSpan.FromMilliseconds((int)m.Value); SetDespawnTime(timespan); } @@ -103,7 +107,7 @@ protected override void OnUpdate(TimeSpan time) //ezt kell hivogatni requestbol, ha valtozott public void ReloadRegistration() { - _charactersRegisterHelper.ReloadRegistration(); + _charactersRegisterHelper.ReloadRegistration(); } public Character[] GetRegisteredCharacters() @@ -138,7 +142,7 @@ public virtual void OnDeviceDead() Zone.UnitService.RemoveUserUnit(this); Logger.Info("probe got deleted " + Eid); } - + public virtual void OnDeviceCreated() { //elso info arrol hogy letrejott @@ -146,7 +150,7 @@ public virtual void OnDeviceCreated() Logger.Info("probe created " + Eid); SendDeviceCreated(); - + } @@ -154,13 +158,19 @@ public virtual void OnUnitsFound(List unitsFound) { //itt lehet mindenfele, pl most kuldunk egy kommandot amire a kliens terkepet frissit - if (unitsFound.Count <= 0) return; + if (unitsFound.Count <= 0) + { + return; + } - var registerdCharacters = GetRegisteredCharacters(); + Character[] registerdCharacters = GetRegisteredCharacters(); - if (registerdCharacters.Length <= 0) return; + if (registerdCharacters.Length <= 0) + { + return; + } - var infoDict = CreateInfoDictionaryForProximityProbe(unitsFound); + Dictionary infoDict = CreateInfoDictionaryForProximityProbe(unitsFound); Message.Builder.SetCommand(Commands.ProximityProbeInfo).WithData(infoDict).ToCharacters(registerdCharacters).Send(); } @@ -175,9 +185,9 @@ protected override void OnDead(Unit killer) public Dictionary GetProbeInfo(bool includeRegistered = true) { - var info = BaseInfoToDictionary(); + Dictionary info = BaseInfoToDictionary(); - var probeDict = new Dictionary(); + Dictionary probeDict = new Dictionary(); if (includeRegistered) { @@ -202,20 +212,20 @@ public override Dictionary ToDictionary() public void SendDeviceCreated() { - var membersToInfom = GetAllPossibleMembersToInfom(); + IEnumerable membersToInfom = GetAllPossibleMembersToInfom(); Message.Builder.SetCommand(Commands.ProximityProbeCreated).WithData(ToDictionary()).ToCharacters(membersToInfom).Send(); } public void SendUpdateToAllPossibleMembers() { - var members = GetAllPossibleMembersToInfom(); + IEnumerable members = GetAllPossibleMembersToInfom(); Message.Builder.SetCommand(Commands.ProximityProbeUpdate).WithData(ToDictionary()).ToCharacters(members).Send(); } private void SendDeviceDead() { - var membersToInfom = GetAllPossibleMembersToInfom(); + IEnumerable membersToInfom = GetAllPossibleMembersToInfom(); Message.Builder.SetCommand(Commands.ProximityProbeDead).WithData(ToDictionary()).ToCharacters(membersToInfom).Send(); } @@ -228,19 +238,19 @@ public IEnumerable GetAllPossibleMembersToInfom() private IEnumerable GetProximityBoard(long corporationEid) { const CorporationRole roleMask = CorporationRole.CEO | CorporationRole.DeputyCEO | CorporationRole.Accountant; - return CorporationManager.LoadCorporationMembersWithAnyRole(corporationEid,roleMask); + return CorporationManager.LoadCorporationMembersWithAnyRole(corporationEid, roleMask); } - public Dictionary CreateInfoDictionaryForProximityProbe( List unitsFound) + public Dictionary CreateInfoDictionaryForProximityProbe(List unitsFound) { - var infoDict = GetProbeInfo(false); + Dictionary infoDict = GetProbeInfo(false); - var unitsInfo = unitsFound.ToDictionary("c", p => + Dictionary unitsInfo = unitsFound.ToDictionary("c", p => { return new Dictionary { {k.characterID, p.Character.Id}, - {k.x, p.CurrentPosition.X}, + {k.x, p.CurrentPosition.X}, {k.y, p.CurrentPosition.Y} }; }); @@ -250,7 +260,7 @@ public Dictionary CreateInfoDictionaryForProximityProbe( List

summonerCharacters ) + public void Init(IEnumerable summonerCharacters) { PBSRegisterHelper.WriteRegistersToDb(Eid, summonerCharacters); _probingInterval.Interval = TimeSpan.FromMilliseconds(GetProximityCheckInterval()); @@ -258,7 +268,7 @@ public void Init(IEnumerable summonerCharacters ) public virtual int GetProximityCheckInterval() { - var config = EntityDefault.Get(Definition).Config; + DefinitionConfig config = EntityDefault.Get(Definition).Config; if (config.cycle_time == null) { @@ -266,7 +276,7 @@ public virtual int GetProximityCheckInterval() return 150000; } - return ((int) config.cycle_time) + FastRandom.NextInt(0,250); + return ((int)config.cycle_time) + FastRandom.NextInt(0, 250); } public bool IsRegistered(Character character) @@ -281,35 +291,30 @@ public ErrorCodes HasAccess(Character character) return ErrorCodes.NoError; } - var corporationEid = character.CorporationEid; + long corporationEid = character.CorporationEid; if (corporationEid != Owner) { return ErrorCodes.AccessDenied; } - var role = Corporation.GetRoleFromSql(character); - - if (IsAllProbesVisible(role)) - { - return ErrorCodes.NoError; - } + CorporationRole role = Corporation.GetRoleFromSql(character); - return ErrorCodes.AccessDenied; + return IsAllProbesVisible(role) ? ErrorCodes.NoError : ErrorCodes.AccessDenied; } public Dictionary GetProbeRegistrationInfo() { - var ownerCorporation = Corporation.GetOrThrow(Owner); - var maxRegistered = ownerCorporation.GetMaximumRegisteredProbesAmount(); - var currentRegistered = GetRegisteredCharacters().Length; - var boardMembers = ownerCorporation.GetBoardMembersCount(); + Corporation ownerCorporation = Corporation.GetOrThrow(Owner); + int maxRegistered = ownerCorporation.GetMaximumRegisteredProbesAmount(); + int currentRegistered = GetRegisteredCharacters().Length; + int boardMembers = ownerCorporation.GetBoardMembersCount(); - var result = new Dictionary + Dictionary result = new Dictionary { {k.eid, Eid }, {"maxRegistered", maxRegistered}, - {"freeSlots", (maxRegistered - (currentRegistered - boardMembers).Clamp(0, int.MaxValue))}, + {"freeSlots", maxRegistered - (currentRegistered - boardMembers).Clamp(0, int.MaxValue)}, {"currentlyRegistered", currentRegistered}, {"boardMembers", boardMembers}, }; diff --git a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs index 07654479d..0cfb05719 100644 --- a/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs +++ b/src/Perpetuum/Zones/ProximityProbes/ProximityProbe.cs @@ -1,12 +1,13 @@ -using System.Collections.Generic; -using System.Linq; +using Perpetuum.EntityFramework; using Perpetuum.ExportedTypes; using Perpetuum.Players; using Perpetuum.Zones.Blobs.BlobEmitters; +using System.Collections.Generic; +using System.Linq; namespace Perpetuum.Zones.ProximityProbes { - public class ProximityProbe : ProximityDeviceBase , IBlobEmitter + public class ProximityProbe : ProximityDeviceBase, IBlobEmitter { protected internal override void UpdatePlayerVisibility(Player player) { @@ -15,14 +16,14 @@ protected internal override void UpdatePlayerVisibility(Player player) public override List GetNoticedUnits() { - return GetVisibleUnits().Select(v=>v.Target).OfType().ToList(); + return GetVisibleUnits().Select(v => v.Target).OfType().ToList(); } protected override bool IsActive { get { - var coreRatio = Core.Ratio(CoreMax); + double coreRatio = Core.Ratio(CoreMax); return coreRatio > 0.98; } } @@ -31,7 +32,7 @@ public double BlobEmission { get { - var blobEmission = GetPropertyModifier(AggregateField.blob_emission); + Items.ItemPropertyModifier blobEmission = GetPropertyModifier(AggregateField.blob_emission); return blobEmission.Value; } } @@ -40,9 +41,17 @@ public double BlobEmissionRadius { get { - var blobEmissionRadius = GetPropertyModifier(AggregateField.blob_emission_radius); + Items.ItemPropertyModifier blobEmissionRadius = GetPropertyModifier(AggregateField.blob_emission_radius); return blobEmissionRadius.Value; } } + + public override void AcceptVisitor(IEntityVisitor visitor) + { + if (!TryAcceptVisitor(this, visitor)) + { + base.AcceptVisitor(visitor); + } + } } } \ No newline at end of file From 28e436a3d45d1ed6c06e442dd735fc70d6279020 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Sat, 9 Mar 2024 17:39:20 +0500 Subject: [PATCH 452/457] - Finalizing changes --- src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs | 3 +-- src/Perpetuum/ErrorCodes.cs | 1 + src/Perpetuum/Modules/RemoteControllerModule.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs index c89036b08..c71b95cdf 100644 --- a/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs +++ b/src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs @@ -276,7 +276,6 @@ public IEnumerable GetCommands() public void Init(string gameRoot) { - Thread.Sleep(10000); _builder = new ContainerBuilder(); InitContainer(gameRoot); _container = _builder.Build(); @@ -870,7 +869,7 @@ private void RegisterEntities() #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; }); diff --git a/src/Perpetuum/ErrorCodes.cs b/src/Perpetuum/ErrorCodes.cs index ff7455ef7..39ffed3bf 100644 --- a/src/Perpetuum/ErrorCodes.cs +++ b/src/Perpetuum/ErrorCodes.cs @@ -713,5 +713,6 @@ public enum ErrorCodes RespecTokenIsNotReadyYet, MaxBandwidthExceed, RemoteControlledTurretInRange, + TurretNotDeployableNearObject, } } diff --git a/src/Perpetuum/Modules/RemoteControllerModule.cs b/src/Perpetuum/Modules/RemoteControllerModule.cs index 689c37584..157b9c257 100644 --- a/src/Perpetuum/Modules/RemoteControllerModule.cs +++ b/src/Perpetuum/Modules/RemoteControllerModule.cs @@ -158,13 +158,13 @@ protected override void OnAction() .OfType() .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_BASE) .Any() - .ThrowIfTrue(ErrorCodes.NotDeployableNearObject); + .ThrowIfTrue(ErrorCodes.TurretNotDeployableNearObject); Zone.Units .OfType() .WithinRange(spawnPosition, DistanceConstants.RCU_DEPLOY_RANGE_FROM_TELEPORT) .Any() - .ThrowIfTrue(ErrorCodes.TeleportIsInRange); + .ThrowIfTrue(ErrorCodes.TurretNotDeployableNearObject); } else if (ammo.ED.Options.TurretType == TurretType.Mining || ammo.ED.Options.TurretType == TurretType.Harvesting) { From 8ca9357fcc9f393155e99d66c22538f9ef2a483b Mon Sep 17 00:00:00 2001 From: Line <34966518+Sellafield@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:04:10 +0300 Subject: [PATCH 453/457] - Added override for plants timers (#492) - Admin commands for plants growth will now work not only in server dev mode Co-authored-by: Dolzhukov, Viktor --- src/Perpetuum/Modules/DrillerModule.cs | 6 +- .../ChatCommands/AdminCommandHandlers.cs | 360 ++++++++++++------ src/Perpetuum/Services/RiftSystem/Rift.cs | 74 +++- src/Perpetuum/Units/Unit.cs | 353 ++++++++--------- .../NpcSystem/AI/BodyPullThreatHelper.cs | 72 ++-- .../Zones/RemoteControl/CombatDrone.cs | 38 +- .../Zones/RemoteControl/SentryTurret.cs | 19 + .../Terrains/Materials/Plants/PlantHandler.cs | 55 +-- src/Perpetuum/Zones/ZoneConfiguration.cs | 43 ++- 9 files changed, 596 insertions(+), 424 deletions(-) diff --git a/src/Perpetuum/Modules/DrillerModule.cs b/src/Perpetuum/Modules/DrillerModule.cs index fed8937ad..8429f26a1 100644 --- a/src/Perpetuum/Modules/DrillerModule.cs +++ b/src/Perpetuum/Modules/DrillerModule.cs @@ -204,8 +204,10 @@ private void CheckEnablerEffect(MaterialInfo materialInfo) return; } - bool 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/Services/Channels/ChatCommands/AdminCommandHandlers.cs b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs index 68744bf82..6c4374e9f 100644 --- a/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs +++ b/src/Perpetuum/Services/Channels/ChatCommands/AdminCommandHandlers.cs @@ -42,17 +42,19 @@ private static void HandleLocalRequest(AdminCommandData data, string 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); + _ = bool.TryParse(data.Command.Args[0], out bool adddelete); + _ = bool.TryParse(data.Command.Args[1], out bool keeplock); - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + 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); - var lockedtiles = player.GetLocks(); + List lockedtiles = player.GetLocks(); using (new TerrainUpdateMonitor(zone)) { @@ -73,13 +75,15 @@ private static void ZoneSetTilesControl(AdminCommandData data, TerrainControlFla private static void LockOrUnlockZoneLayers(AdminCommandData data, bool toLock) { if (!IsDevModeEnabled(data)) + { return; + } bool err = false; - var zoneId = -1; + int zoneId = -1; if (data.Command.Args.IsNullOrEmpty()) { - var character = data.Request.Session.Character; + Character character = data.Request.Session.Character; zoneId = character.ZoneId ?? -1; } else if (data.Command.Args.Length >= 1) @@ -94,7 +98,7 @@ private static void LockOrUnlockZoneLayers(AdminCommandData data, bool toLock) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } CheckZoneId(data, zoneId); - var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); zone.IsLayerEditLocked = toLock; SendMessageToAll(data, $"All layers on zone {zoneId} {(toLock ? "LOCKED" : "UNLOCKED")}!"); } @@ -137,8 +141,7 @@ public static void UnSecure(AdminCommandData data) public static void Shutdown(AdminCommandData data) { DateTime shutdownin = DateTime.Now; - int minutes = 1; - if (!int.TryParse(data.Command.Args[1], out minutes)) + if (!int.TryParse(data.Command.Args[1], out int minutes)) { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } @@ -182,7 +185,7 @@ public static void SetMaxSessions(AdminCommandData data) int minSessionBound = 1; int maxSessionBound = 10000; CheckRequiredArgLength(data, 1); - if (!int.TryParse(data.Command.Args[0], out var maxPlayerSessions)) + if (!int.TryParse(data.Command.Args[0], out int maxPlayerSessions)) { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } @@ -191,7 +194,7 @@ public static void SetMaxSessions(AdminCommandData data) SendMessageToAll(data, $"maxPlayerSessions {maxPlayerSessions} is outside of accepted range [{minSessionBound},{maxSessionBound}]"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - var dictionary = new Dictionary() { { k.amount, maxPlayerSessions } }; + Dictionary dictionary = new Dictionary() { { k.amount, maxPlayerSessions } }; string cmd = string.Format("{0}:relay:{1}", Commands.SetMaxUserCount.Text, GenxyConverter.Serialize(dictionary)); HandleLocalRequest(data, cmd); } @@ -212,7 +215,10 @@ public static void JumpTo(AdminCommandData data) catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } @@ -247,14 +253,17 @@ public static void MovePlayer(AdminCommandData data) catch (Exception ex) { if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } CheckZoneId(data, zoneId); // get the target character's session. - var charactersession = data.SessionManager.GetByCharacter(characterId); + Sessions.ISession charactersession = data.SessionManager.GetByCharacter(characterId); if (charactersession.Character.ZoneId == null) { @@ -263,7 +272,7 @@ public static void MovePlayer(AdminCommandData data) } // get destination zone. - var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); if (charactersession.Character.ZoneId == null) { @@ -275,24 +284,22 @@ public static void MovePlayer(AdminCommandData data) TeleportToAnotherZone tp = new TeleportToAnotherZone(zone); // we need the player (robot, etc) to teleport on the origin zone - var player = data.Request.Session.ZoneMgr.GetZone((int)charactersession.Character.ZoneId).GetPlayer(charactersession.Character.ActiveRobotEid); + 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); - tp = null; - + _ = 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); + _ = int.TryParse(data.Command.Args[0], out int definition); + _ = int.TryParse(data.Command.Args[1], out int qty); Dictionary dictionary = new Dictionary() { @@ -307,11 +314,11 @@ public static void GiveItem(AdminCommandData data) [ChatCommand("GetLockedTileProperties")] public static void GetLockedTileProperties(AdminCommandData data) { - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); - var lockedtile = player.GetPrimaryLock(); + Lock lockedtile = player.GetPrimaryLock(); TerrainControlInfo ti = zone.Terrain.Controls.GetValue((lockedtile as TerrainLock).Location); @@ -360,11 +367,11 @@ public static void GetLockedTileProperties(AdminCommandData data) public static void SetVisibility(AdminCommandData data) { CheckRequiredArgLength(data, 1); - bool.TryParse(data.Command.Args[0], out bool visiblestate); + _ = bool.TryParse(data.Command.Args[0], out bool visiblestate); - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + 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; @@ -373,13 +380,13 @@ public static void SetVisibility(AdminCommandData data) [ChatCommand("ZoneDrawStatMap")] public static void ZoneDrawStatMap(AdminCommandData data) { - var zoneId = data.Sender.ZoneId ?? -1; + int zoneId = data.Sender.ZoneId ?? -1; if (data.Command.Args.Length < 1) { SendMessageToAll(data, $"Missing 'type' arg"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - var type = data.Command.Args[0]; + string type = data.Command.Args[0]; if (data.Command.Args.Length > 1) { if (!int.TryParse(data.Command.Args[1], out zoneId)) @@ -389,7 +396,7 @@ public static void ZoneDrawStatMap(AdminCommandData data) } } CheckZoneId(data, zoneId); - var dictionary = new Dictionary() { { k.type, type } }; + 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); @@ -398,7 +405,7 @@ public static void ZoneDrawStatMap(AdminCommandData data) [ChatCommand("ListAllPlayersInZone")] public static void ListAllPlayersInZone(AdminCommandData data) { - int.TryParse(data.Command.Args[0], out int zoneid); + _ = int.TryParse(data.Command.Args[0], out int zoneid); CheckZoneId(data, zoneid); SendMessageToAll(data, string.Format("Players On Zone {0}", zoneid)); @@ -420,9 +427,9 @@ public static void CountOfPlayers(AdminCommandData data) [ChatCommand("AddToChannel")] public static void AddToChannel(AdminCommandData data) { - int.TryParse(data.Command.Args[0], out int characterid); + _ = int.TryParse(data.Command.Args[0], out int characterid); - var c = data.SessionManager.GetByCharacter(characterid); + Sessions.ISession c = data.SessionManager.GetByCharacter(characterid); data.ChannelManager.JoinChannel(data.Channel.Name, c.Character, ChannelMemberRole.Operator, string.Empty); @@ -431,9 +438,9 @@ public static void AddToChannel(AdminCommandData data) [ChatCommand("RemoveFromChannel")] public static void RemoveFromChannel(AdminCommandData data) { - int.TryParse(data.Command.Args[0], out int characterid); + _ = int.TryParse(data.Command.Args[0], out int characterid); - var c = data.SessionManager.GetByCharacter(characterid); + Sessions.ISession c = data.SessionManager.GetByCharacter(characterid); data.ChannelManager.LeaveChannel(data.Channel.Name, c.Character); @@ -444,7 +451,7 @@ public static void ListRifts(AdminCommandData data) { foreach (IZone z in data.Request.Session.ZoneMgr.Zones) { - var rift = z.Units.OfType(); + IEnumerable rift = z.Units.OfType(); foreach (Rift r in rift) { SendMessageToAll(data, string.Format("Rift - Zone: {0}, Position: ({1})", r.Zone, r.CurrentPosition)); @@ -467,12 +474,15 @@ public static void FlagPlayerNameOffensive(AdminCommandData data) 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? - var characterSession = data.SessionManager.GetByCharacter(characterId); + 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)); @@ -487,7 +497,7 @@ public static void RenameCorp(AdminCommandData data) CheckRequiredArgLength(data, 3); Corporation.IsNameOrNickTaken(desiredCorpName, desiredCorpNick).ThrowIfTrue(ErrorCodes.NameTaken); - var corp = Corporation.GetByName(currentCorpName); + Corporation corp = Corporation.GetByName(currentCorpName); corp.SetName(desiredCorpName, desiredCorpNick); SendMessageToAll(data, string.Format("Corp with nick {0} changed to: {1} [{2}]", currentCorpName, desiredCorpName, desiredCorpNick)); @@ -504,7 +514,10 @@ public static void UnlockAllEP(AdminCommandData data) catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } @@ -533,7 +546,10 @@ public static void EPBonusSet(AdminCommandData data) catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } @@ -550,21 +566,19 @@ public static void EPBonusSet(AdminCommandData data) [ChatCommand("ListRelics")] public static void ListRelics(AdminCommandData data) { - bool err = false; - - var character = data.Request.Session.Character; + Character character = data.Request.Session.Character; IZone zone = null; if (data.Command.Args.Length == 1) { - err = !int.TryParse(data.Command.Args[0], out int zoneCommand); + bool err = !int.TryParse(data.Command.Args[0], out int zoneCommand); if (err) { SendMessageToAll(data, "Bad args"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - var zoneid = zoneCommand; - zone = data.Request.Session.ZoneMgr.GetZone((int)zoneid); + int zoneid = zoneCommand; + zone = data.Request.Session.ZoneMgr.GetZone(zoneid); } else if (character.ZoneId != null) { @@ -577,14 +591,14 @@ public static void ListRelics(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - Dictionary dictionary = new Dictionary() + _ = new Dictionary() { {"zoneid", zone.Id } }; if (zone.RelicManager != null) { - var relicDictList = zone.RelicManager.GetRelicListDictionary(); - foreach (var dict in relicDictList) + List> relicDictList = zone.RelicManager.GetRelicListDictionary(); + foreach (Dictionary dict in relicDictList) { SendMessageToAll(data, dict.ToDebugString()); } @@ -603,7 +617,7 @@ public static void SetWeather(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - var zoneId = data.Sender.ZoneId ?? -1; + int zoneId = data.Sender.ZoneId ?? -1; int weatherInt; int seconds; try @@ -611,26 +625,31 @@ public static void SetWeather(AdminCommandData data) 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); - var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); - var current = zone.Weather.GetCurrentWeather(); - var weather = new WeatherInfo(current.Next, weatherInt.Min(255), TimeSpan.FromSeconds(seconds)); + 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().ToString()}"); + SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather()}"); } [ChatCommand("GetWeather")] public static void GetWeather(AdminCommandData data) { - var zoneId = data.Sender.ZoneId ?? -1; + int zoneId = data.Sender.ZoneId ?? -1; bool err = false; if (!data.Command.Args.IsNullOrEmpty()) { @@ -642,8 +661,8 @@ public static void GetWeather(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } CheckZoneId(data, zoneId); - var zone = data.Request.Session.ZoneMgr.GetZone(zoneId); - SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather().ToString()}"); + IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId); + SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather()}"); } #endregion #region DevCommands @@ -651,7 +670,9 @@ public static void GetWeather(AdminCommandData data) public static void ZoneCleanObstacleBlocking(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } string cmd = string.Format("zoneCleanObstacleBlocking:zone_{0}:null", data.Sender.ZoneId); HandleLocalRequest(data, cmd); @@ -660,10 +681,10 @@ public static void ZoneCleanObstacleBlocking(AdminCommandData data) public static void ZoneDrawBlockingByEid(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; - - bool err = false; - err = !Int64.TryParse(data.Command.Args[0], out Int64 eid); + } + bool err = !long.TryParse(data.Command.Args[0], out long eid); if (err) { @@ -682,10 +703,11 @@ public static void ZoneDrawBlockingByEid(AdminCommandData data) public static void ZoneRemoveObjectByEid(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - bool err = false; - err = !Int64.TryParse(data.Command.Args[0], out Int64 eid); + _ = !long.TryParse(data.Command.Args[0], out long eid); Dictionary dictionary = new Dictionary() { @@ -699,7 +721,9 @@ public static void ZoneRemoveObjectByEid(AdminCommandData data) public static void ZoneCreateIsland(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } int zoneId = data.Sender.ZoneId ?? -1; int lvl; @@ -707,17 +731,22 @@ public static void ZoneCreateIsland(AdminCommandData data) { 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); - var dictionary = new Dictionary() { { k.low, lvl } }; + 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... "); @@ -728,27 +757,36 @@ public static void ZoneCreateIsland(AdminCommandData data) public static void ZoneCreateTerraformLimit(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - int zoneId = data.Sender.ZoneId ?? -1; + _ = 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); - var dictionary = new Dictionary() + Dictionary dictionary = new Dictionary() { { k.distance, radius }, { k.mode, mode } @@ -763,14 +801,17 @@ public static void ZoneCreateTerraformLimit(AdminCommandData data) public static void ZoneSetLayerWithBitMap(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - int zoneId = data.Sender.ZoneId ?? -1; + _ = data.Sender.ZoneId ?? -1; string fileName; int flagValue; + int zoneId; try { - var flagName = data.Command.Args[0]; + string flagName = data.Command.Args[0]; fileName = data.Command.Args[1]; flagValue = (int)EnumHelper.GetEnumFromName(flagName); zoneId = int.Parse(data.Command.Args[2]); @@ -778,12 +819,15 @@ public static void ZoneSetLayerWithBitMap(AdminCommandData data) catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } CheckZoneId(data, zoneId); - var dictionary = new Dictionary() + Dictionary dictionary = new Dictionary() { { k.file, fileName }, { k.flags, flagValue } @@ -798,7 +842,9 @@ public static void ZoneSetLayerWithBitMap(AdminCommandData data) public static void ZonePlaceWall(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } string cmd = string.Format("zonePlaceWall:zone_{0}:null", data.Sender.ZoneId); HandleLocalRequest(data, cmd); @@ -807,7 +853,9 @@ public static void ZonePlaceWall(AdminCommandData data) public static void ZoneClearWalls(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } string cmd = string.Format("zoneClearWalls:zone_{0}:null", data.Sender.ZoneId); HandleLocalRequest(data, cmd); @@ -816,7 +864,9 @@ public static void ZoneClearWalls(AdminCommandData data) public static void ZoneAddDecor(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } int? zoneId = data.Sender.ZoneId; int definition; @@ -833,7 +883,10 @@ public static void ZoneAddDecor(AdminCommandData data) 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]); @@ -848,7 +901,10 @@ public static void ZoneAddDecor(AdminCommandData data) catch (Exception ex) { if (ex is FormatException || ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } @@ -877,14 +933,15 @@ string cmd() public static void ZoneAddDecorToLockedTile(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); - var terrainLock = player.GetPrimaryLock() as TerrainLock; - if (terrainLock == null) + if (!(player.GetPrimaryLock() is TerrainLock terrainLock)) { return; } @@ -904,7 +961,10 @@ public static void ZoneAddDecorToLockedTile(AdminCommandData data) catch (Exception ex) { if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } @@ -929,10 +989,11 @@ public static void ZoneAddDecorToLockedTile(AdminCommandData data) public static void ZoneDeleteDecor(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int idno); + _ = !int.TryParse(data.Command.Args[0], out int idno); Dictionary dictionary = new Dictionary() { @@ -946,7 +1007,9 @@ public static void ZoneDeleteDecor(AdminCommandData data) public static void ZoneClearLayer(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } string layerName; int zoneId = data.Sender.ZoneId ?? -1; @@ -954,31 +1017,37 @@ public static void ZoneClearLayer(AdminCommandData data) { 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); - var dictionary = new Dictionary() { { k.layerName, layerName } }; - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneClearLayer.Text, zoneId, GenxyConverter.Serialize(dictionary)); + 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)) + if (!IsDevModeEnabled(data) && data.Sender.AccessLevel == AccessLevel.normal) + { return; + } - bool err = false; - err = !int.TryParse(data.Command.Args[0], out int speed); + _ = !int.TryParse(data.Command.Args[0], out int speed); Dictionary dictionary = new Dictionary() { @@ -991,8 +1060,10 @@ public static void ZoneSetPlantSpeed(AdminCommandData data) [ChatCommand("ZoneSetPlantMode")] public static void ZoneSetPlantMode(AdminCommandData data) { - if (!IsDevModeEnabled(data)) + if (!IsDevModeEnabled(data) && data.Sender.AccessLevel == AccessLevel.normal) + { return; + } Dictionary dictionary = new Dictionary() { @@ -1006,9 +1077,11 @@ public static void ZoneSetPlantMode(AdminCommandData data) public static void ZoneRestoreOriginalGamma(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - var dictionary = new Dictionary() + _ = new Dictionary() { { "mode", data.Command.Args[0] } }; @@ -1020,13 +1093,15 @@ public static void ZoneRestoreOriginalGamma(AdminCommandData data) public static void ZoneDrawBlockingByDefinition(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } int zoneId = data.Sender.ZoneId ?? -1; int[] defs; try { - var list = new List(); + 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(); } @@ -1034,12 +1109,15 @@ public static void ZoneDrawBlockingByDefinition(AdminCommandData data) { SendMessageToAll(data, "Bad args"); if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } CheckZoneId(data, zoneId); - var dictionary = new Dictionary() + Dictionary dictionary = new Dictionary() { { k.definition, defs } }; @@ -1051,13 +1129,15 @@ public static void ZoneDrawBlockingByDefinition(AdminCommandData data) public static void ZoneAddBlockingToLockedTiles(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + 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); - var lockedtiles = player.GetLocks(); + List lockedtiles = player.GetLocks(); using (new TerrainUpdateMonitor(zone)) { @@ -1075,13 +1155,15 @@ public static void ZoneAddBlockingToLockedTiles(AdminCommandData data) public static void ZoneRemoveBlockingToLockedTiles(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + 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); - var lockedtiles = player.GetLocks(); + List lockedtiles = player.GetLocks(); using (new TerrainUpdateMonitor(zone)) { @@ -1099,7 +1181,9 @@ public static void ZoneRemoveBlockingToLockedTiles(AdminCommandData data) public static void ZoneLockDecor(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } int id; int locked; @@ -1112,7 +1196,10 @@ public static void ZoneLockDecor(AdminCommandData data) catch (Exception ex) { if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } @@ -1184,13 +1271,14 @@ public static void ZoneSetTilesTerraformProtectCombo(AdminCommandData data) public static void SaveLayers(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - bool err = false; - var dictionary = new Dictionary(); + Dictionary dictionary = new Dictionary(); if (data.Command.Args.Length == 1) { - err = !int.TryParse(data.Command.Args[0], out int zoneId); + bool err = !int.TryParse(data.Command.Args[0], out int zoneId); if (err) { SendMessageToAll(data, "Bad args"); @@ -1198,7 +1286,7 @@ public static void SaveLayers(AdminCommandData data) } dictionary.Add(k.zoneID, zoneId); } - var cmd = string.Format("{0}:relay:{1}", Commands.ZoneSaveLayer.Text, GenxyConverter.Serialize(dictionary)); + 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! "); @@ -1207,24 +1295,25 @@ public static void SaveLayers(AdminCommandData data) public static void ZoneIslandBlock(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - bool err = false; - var dictionary = new Dictionary(); + Dictionary dictionary = new Dictionary(); if (data.Command.Args.Length != 1) { SendMessageToAll(data, "Missing or too many args"); throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } - err = !int.TryParse(data.Command.Args[0], out int low); + 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); - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateIsland.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + 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! "); @@ -1233,12 +1322,14 @@ public static void ZoneIslandBlock(AdminCommandData data) public static void ZoneCreateGarden(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } int x; int y; - var dictionary = new Dictionary(); + Dictionary dictionary = new Dictionary(); if (data.Command.Args.Length != 2) { SendMessageToAll(data, "Missing or too many args"); @@ -1254,13 +1345,16 @@ public static void ZoneCreateGarden(AdminCommandData data) { SendMessageToAll(data, "Bad args"); if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } dictionary.Add(k.x, x); dictionary.Add(k.y, y); - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneCreateGarden.Text, data.Sender.ZoneId, GenxyConverter.Serialize(dictionary)); + 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! "); @@ -1269,11 +1363,13 @@ public static void ZoneCreateGarden(AdminCommandData data) public static void ZoneClearGroundType(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } bool err = false; - var zoneId = data.Sender.ZoneId ?? -1; + int zoneId = data.Sender.ZoneId ?? -1; if (data.Command.Args.Length > 0) { err = !int.TryParse(data.Command.Args[0], out zoneId); @@ -1284,8 +1380,8 @@ public static void ZoneClearGroundType(AdminCommandData data) throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); } CheckZoneId(data, zoneId); - var dictionary = new Dictionary() { { k.layerName, k.groundType } }; - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneClearLayer.Text, zoneId, GenxyConverter.Serialize(dictionary)); + 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"); @@ -1294,7 +1390,9 @@ public static void ZoneClearGroundType(AdminCommandData data) public static void ZoneCopyGroundType(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } CheckRequiredArgLength(data, 2); int sourceZone; @@ -1308,7 +1406,10 @@ public static void ZoneCopyGroundType(AdminCommandData data) { SendMessageToAll(data, "Bad args"); if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } if (sourceZone == targetZone) @@ -1318,8 +1419,8 @@ public static void ZoneCopyGroundType(AdminCommandData data) } CheckZoneId(data, sourceZone); CheckZoneId(data, targetZone); - var dictionary = new Dictionary() { { k.source, sourceZone }, { k.target, targetZone } }; - var cmd = string.Format("{0}:relay:{1}", Commands.ZoneCopyGroundType.Text, GenxyConverter.Serialize(dictionary)); + 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"); @@ -1328,7 +1429,9 @@ public static void ZoneCopyGroundType(AdminCommandData data) public static void ZoneRandomFillGroundType(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } int radius = 200; int iterations = 10; @@ -1356,11 +1459,11 @@ public static void ZoneRandomFillGroundType(AdminCommandData data) } CheckZoneId(data, zoneId); - var dictionary = new Dictionary(){ + Dictionary dictionary = new Dictionary(){ { k.size, radius }, { k.numberOfRuns, iterations } }; - var cmd = string.Format("{0}:zone_{1}:{2}", Commands.ZoneFillGroundTypeRandom.Text, zoneId, GenxyConverter.Serialize(dictionary)); + 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"); @@ -1369,25 +1472,32 @@ public static void ZoneRandomFillGroundType(AdminCommandData data) 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); - var cmd = string.Format("{0}:zone_{1}:null", Commands.ZoneSmooth.Text, zoneId); + string cmd = string.Format("{0}:zone_{1}:null", Commands.ZoneSmooth.Text, zoneId); SendMessageToAll(data, $"Sending: {cmd}"); HandleLocalRequest(data, cmd); SendMessageToAll(data, $"Command completed"); @@ -1406,14 +1516,16 @@ public static void ZoneUnlockLayers(AdminCommandData data) 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); + _ = 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 }, @@ -1432,13 +1544,14 @@ public static void TestMissions(AdminCommandData data) public static void SpawnRelic(AdminCommandData data) { if (!IsDevModeEnabled(data)) + { return; + } - var character = data.Request.Session.Character; - var zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); - var player = zone.GetPlayer(character.ActiveRobotEid); + Character character = data.Request.Session.Character; + IZone zone = data.Request.Session.ZoneMgr.GetZone((int)character.ZoneId); + Players.Player player = zone.GetPlayer(character.ActiveRobotEid); - var terrainLock = player.GetPrimaryLock() as TerrainLock; int x, y, zoneid; @@ -1456,11 +1569,14 @@ public static void SpawnRelic(AdminCommandData data) { SendMessageToAll(data, "Bad args"); if (ex is ArgumentNullException) + { throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified); + } + throw; } - if (terrainLock == null) + if (!(player.GetPrimaryLock() is TerrainLock terrainLock)) { if (data.Command.Args.Length != 3) { @@ -1470,7 +1586,7 @@ public static void SpawnRelic(AdminCommandData data) x = xCommand; y = yCommand; zoneid = zoneCommand; - zone = data.Request.Session.ZoneMgr.GetZone((int)zoneid); + zone = data.Request.Session.ZoneMgr.GetZone(zoneid); if (zone == null) { SendMessageToAll(data, "Bad zone id!"); diff --git a/src/Perpetuum/Services/RiftSystem/Rift.cs b/src/Perpetuum/Services/RiftSystem/Rift.cs index 5a0fe787b..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,6 +9,11 @@ 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 { @@ -38,6 +39,19 @@ public virtual void UseItem(Player player) 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); + } + } } ///

@@ -80,7 +94,9 @@ 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) @@ -89,7 +105,7 @@ public void SetTarget(IZone zone, Position position) TargetPosition = position; } - private bool HasTarget { get { return TargetZone != null && !TargetPosition.Equals(Position.Empty); } } + private bool HasTarget => TargetZone != null && !TargetPosition.Equals(Position.Empty); private bool IsUsageExceeded() { @@ -99,7 +115,9 @@ private bool IsUsageExceeded() private void IncrementUsage() { if (!RiftConfig.InfiniteUses) + { uses++; + } } private bool IsExcluded(Player player) @@ -116,14 +134,16 @@ public override void UseItem(Player player) IsExcluded(player).ThrowIfTrue(ErrorCodes.RobotWrongType); - var teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(TargetZone); + TeleportToAnotherZone teleport = _teleportStrategyFactories.TeleportToAnotherZoneFactory(TargetZone); teleport.TargetPosition = TargetPosition; - teleport.DoTeleportAsync(player); + _ = teleport.DoTeleportAsync(player); IncrementUsage(); if (IsUsageExceeded()) + { Kill(); + } } } @@ -152,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; @@ -165,11 +185,11 @@ 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); } } @@ -188,17 +208,23 @@ 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); + } if (ED.Tier.level > rift.MaxTAPLevel) + { throw new PerpetuumException(ErrorCodes.RiftLevelMismatch); + } if (zone is StrongHoldZone) + { throw new PerpetuumException(ErrorCodes.ItemNotUsable); + } - var info = new RiftNpcGroupInfo(); + 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"); @@ -208,11 +234,13 @@ public override void Deploy(IZone zone, Player player) info.ownerPlayer = player; if (!rift.TryActivate(info)) + { throw new PerpetuumException(ErrorCodes.WTFErrorMedicalAttentionSuggested); + } 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); } } @@ -247,21 +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; @@ -279,17 +313,17 @@ public override void UseItem(Player player) { 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); } diff --git a/src/Perpetuum/Units/Unit.cs b/src/Perpetuum/Units/Unit.cs index 70f3deaa4..5f06b0699 100644 --- a/src/Perpetuum/Units/Unit.cs +++ b/src/Perpetuum/Units/Unit.cs @@ -8,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; @@ -22,6 +23,7 @@ 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; @@ -46,15 +48,17 @@ 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); if (_owner.ActualMass > 0) + { speedMax.Multiply(_owner.Mass / _owner.ActualMass); + } _owner.ApplyEffectPropertyModifiers(AggregateField.effect_speed_highway_modifier, ref speedMax); return speedMax.Value; @@ -86,9 +90,6 @@ 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; @@ -126,11 +127,11 @@ 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; }; @@ -150,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; @@ -176,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; } @@ -188,7 +188,9 @@ public double CurrentSpeed set { if (Math.Abs(_currentSpeed - value) < double.Epsilon) + { return; + } _currentSpeed = value; UpdateTypes |= UnitUpdateTypes.Speed; @@ -201,7 +203,9 @@ public double Direction set { if (Math.Abs(_direction - value) < double.Epsilon) + { return; + } _direction = value; UpdateTypes |= UnitUpdateTypes.Direction; @@ -214,7 +218,9 @@ public double Orientation set { if (Math.Abs(_orientation - value) < double.Epsilon) + { return; + } _orientation = value; UpdateTypes |= UnitUpdateTypes.Orientation; @@ -225,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; - - return ErrorCodes.TargetIsNonAttackable; - } - } + public virtual ErrorCodes IsAttackable => !ED.AttributeFlags.NonAttackable && !States.Dead ? ErrorCodes.NoError : 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); @@ -252,39 +246,37 @@ public bool IsInvulnerable public bool HasNoTeleportWhilePVP => EffectHandler.Effects.Any(e => e.PropertyModifiers.Any(p => p.Field == AggregateField.pvp_no_teleport)); - public Position WorldPosition - { - get { return Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; } - } + public Position WorldPosition => Zone?.ToWorldPosition(CurrentPosition) ?? CurrentPosition; - public Position PositionWithHeight - { - get { return CurrentPosition.AddToZ(Height); } - } + 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); } @@ -301,7 +293,9 @@ public Position CurrentPosition public void Update(TimeSpan time) { if (!InZone || States.Dead) + { return; + } OnUpdate(time); } @@ -316,7 +310,7 @@ protected virtual void OnUpdate(TimeSpan time) UnitUpdatedEventArgs e = null; - _broadcastTimer.Update(time); + _ = _broadcastTimer.Update(time); if (_broadcastTimer.Passed) { @@ -328,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); } @@ -366,10 +364,10 @@ 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()); } } @@ -389,7 +387,9 @@ public void AddToZone(IZone zone, Position position, ZoneEnterType enterType = Z zone.AddUnit(this); if (enterBeamBuilder != null) + { zone.CreateBeam(enterBeamBuilder); + } EnterPacketBuilder = UnitEnterPacketBuilder.Create(this, enterType); zone.UpdateUnitRelations(this); @@ -438,14 +438,16 @@ protected virtual void OnDamageTaken(Unit source, DamageTakenEventArgs 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; @@ -482,15 +484,19 @@ private void OnUpdated(UnitUpdatedEventArgs e) protected virtual void DoExplosion() { - var zone = Zone; + IZone zone = Zone; if (zone == null) + { return; + } 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)); } /// @@ -502,29 +508,33 @@ protected virtual void DoExplosion() 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); + return (-3.336453 + (1.617863 * x) - (0.09721877 * (x * x)) + (0.002064723 * (x * x * x))).Clamp(1.0, 30.0); } private IDamageBuilder GetExplosionDamageBuilder() { - var radius = SigRadiusToExplosionRadius(SignatureRadius); - var damageBuilder = DamageInfo.Builder.WithAttacker(this) + 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; } @@ -562,23 +572,29 @@ public void Visit(PBSObject pbsObject) public void Kill(Unit killer = null) { 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()); @@ -631,17 +647,19 @@ protected virtual void OnCellChanged(CellCoord lastCellCoord, CellCoord currentC public override Dictionary ToDictionary() { - var result = base.ToDictionary(); + Dictionary result = base.ToDictionary(); 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; @@ -676,7 +694,7 @@ public double GetKersByDamageType(DamageType damageType) public double GetResistByDamageType(DamageType damageType) { - var resist = 0.0; + double resist = 0.0; switch (damageType) { @@ -686,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; } @@ -709,10 +727,10 @@ 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); } @@ -733,7 +751,7 @@ public virtual bool IsWalkable(int x, int y) public virtual IDictionary GetDebugInfo() { - var info = new Dictionary + Dictionary info = new Dictionary { {k.eid, Eid}, {k.definitionName, ED.Name}, @@ -743,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; @@ -757,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); } @@ -776,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); @@ -787,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); @@ -796,14 +814,13 @@ 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); @@ -816,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); } @@ -830,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()); @@ -865,16 +881,16 @@ private static void WriteRobotComponent(BinaryWriter bw, RobotComponent componen bw.Write(component.Definition); 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); } @@ -893,29 +909,17 @@ 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; @@ -943,6 +947,21 @@ public bool IsHostile(Unit unit) 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() { CurrentSpeed = 0; @@ -950,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) @@ -968,96 +984,54 @@ 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 - { - get { return _speedMax.Value; } - } + public double MaxSpeed => _speedMax.Value; public TimeSpan CoreRechargeTime => TimeSpan.FromSeconds(_coreRechargeTime.Value); @@ -1086,7 +1060,10 @@ private void InitUnitProperties() _core.PropertyChanged += property => { if (property.Value > 1.0) + { return; + } + EffectHandler.RemoveEffectsByCategory(EffectCategory.effcat_zero_core_drop); }; AddProperty(_core); @@ -1163,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); } @@ -1184,7 +1161,7 @@ protected override void OnPropertyChanging(ref double newValue) return; } - var armorMax = owner.ArmorMax; + double armorMax = owner.ArmorMax; if (newValue >= armorMax) { newValue = armorMax; @@ -1194,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; } @@ -1218,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)) { @@ -1242,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) @@ -1264,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; @@ -1282,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/Zones/NpcSystem/AI/BodyPullThreatHelper.cs b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs index 01f85849e..775f3fcdb 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/BodyPullThreatHelper.cs @@ -1,11 +1,13 @@ 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 @@ -15,7 +17,8 @@ public class BodyPullThreatHelper : IEntityVisitor, IEntityVisitor, IEntityVisitor, - IEntityVisitor + IEntityVisitor, + IEntityVisitor { private readonly SmartCreature smartCreature; @@ -98,61 +101,30 @@ public void Visit(AreaBomb bomb) public void Visit(Npc npc) { - 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 == npc.Eid)) - { - return; - } - - if (!smartCreature.IsInAggroRange(npc)) - { - return; - } - - double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); - - smartCreature.AddThreat(npc, new Threat(ThreatType.Bodypull, threat)); + ProcessRcuThreats(npc); } public void Visit(SentryTurret sentryTurret) { - 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 == sentryTurret.Eid)) - { - return; - } + ProcessRcuThreats(sentryTurret); + } - if (!smartCreature.IsInAggroRange(sentryTurret)) - { - return; - } + public void Visit(CombatDrone combatDrone) + { + ProcessRcuThreats(combatDrone); + } - double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); + public void Visit(Portal portal) + { + ProcessRcuThreats(portal); + } - smartCreature.AddThreat(sentryTurret, new Threat(ThreatType.Bodypull, threat)); + public void Visit(MobileTeleport teleport) + { + ProcessRcuThreats(teleport); } - public void Visit(CombatDrone combatDrone) + private void ProcessRcuThreats(Unit unit) { if (smartCreature.Behavior.Type != BehaviorType.RemoteControlledTurret && smartCreature.Behavior.Type != BehaviorType.RemoteControlledDrone) @@ -165,19 +137,19 @@ public void Visit(CombatDrone combatDrone) return; } - if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == combatDrone.Eid)) + if (smartCreature.ThreatManager.Hostiles.Any(h => h.Unit.Eid == unit.Eid)) { return; } - if (!smartCreature.IsInAggroRange(combatDrone)) + if (!smartCreature.IsInAggroRange(unit)) { return; } double threat = Threat.BODY_PULL + FastRandom.NextDouble(0, 5); - smartCreature.AddThreat(combatDrone, new Threat(ThreatType.Bodypull, threat)); + smartCreature.AddThreat(unit, new Threat(ThreatType.Bodypull, threat)); } } } diff --git a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs index 54a84988b..f4585b94e 100644 --- a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs +++ b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs @@ -1,9 +1,13 @@ 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 @@ -70,6 +74,36 @@ 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 @@ -78,7 +112,9 @@ protected override void OnUnitLockStateChanged(Lock @lock) protected override void UpdateUnitVisibility(Unit target) { if (target is Npc || - target is RemoteControlledCreature) + target is RemoteControlledCreature || + target is Portal || + target is AreaBomb) { UpdateVisibility(target); } diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index d67e382c8..6461f2fa2 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -2,7 +2,9 @@ using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; +using Perpetuum.Zones.DamageProcessors; using Perpetuum.Zones.NpcSystem; +using Perpetuum.Zones.NpcSystem.ThreatManaging; using System; namespace Perpetuum.Zones.RemoteControl @@ -70,5 +72,22 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); } + + protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) + { + base.OnDamageTaken(source, e); + + Player player = Zone.ToPlayerOrGetOwnerPlayer(source); + + if (player == null) + { + return; + } + + if (IsHostilePlayer(player)) + { + AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); + } + } } } \ No newline at end of file diff --git a/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs b/src/Perpetuum/Zones/Terrains/Materials/Plants/PlantHandler.cs index 6a7b5ee23..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 @@ -45,12 +45,13 @@ public PlantHandler(IZone zone) { _zone = zone; _areaAmount = zone.Size.Width / AREA_SIZE; //the amount of areas - var _total_area = (zone.Size.Width / (float)AREA_SIZE) * (zone.Size.Height / (float)AREA_SIZE); - var refreshRate = TimeSpan.FromHours(FULL_PLANT_REGEN_PASS / _total_area); + 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(); } @@ -67,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; @@ -121,7 +126,11 @@ private void FullMapScan() //do full map in one round while (!_zoneFinished) { - if (_stopSignal) return; + if (_stopSignal) + { + return; + } + ScanZone(); } @@ -135,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); @@ -163,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()); } } @@ -214,7 +223,7 @@ private void SetSpeedHigh() public TimeSpan RenewSpeed { - set { _plantsTimer = new IntervalTimer(value); } + set => _plantsTimer = new IntervalTimer(value); } private void ResetState() @@ -230,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(); @@ -274,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/ZoneConfiguration.cs b/src/Perpetuum/Zones/ZoneConfiguration.cs index 2a47eec6b..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(); + + List result = new List(); - var result = new List(); + int port = _globalConfiguration.ListenerPort + 1; - var 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), @@ -64,7 +64,8 @@ public IEnumerable GetAll() Note = record.GetValue("note"), TimeLimitMinutes = record.GetValue("timeLimitMinutes"), - PBSTechLimit = record.GetValue("pbsTechLimit") ?? 0 + PBSTechLimit = record.GetValue("pbsTechLimit") ?? 0, + PlantsGrowthTimerOverrideMin = record.GetValue("PlantsGrowthTimerOverrideMin"), }; config.PlantRules = _plantRuleLoader.LoadPlantRulesWithOverrides(config.plantRuleSetId); @@ -86,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; } @@ -110,6 +111,8 @@ public sealed class ZoneConfiguration public int? TimeLimitMinutes { get; set; } public int PBSTechLimit { get; set; } + public int? PlantsGrowthTimerOverrideMin { get; set; } + public ZoneType Type { get; set; } public Dictionary ToDictionary() @@ -148,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); } } @@ -166,10 +169,10 @@ public ZoneStorage GetStorage() public bool IsAlpha => Protected; - public bool IsBeta => (!Protected && !Terraformable); + public bool IsBeta => !Protected && !Terraformable; public bool IsGamma => Terraformable; } } - + From 1b9145811d7655528d9701d3b7948726db961c17 Mon Sep 17 00:00:00 2001 From: Line <34966518+Sellafield@users.noreply.github.com> Date: Sat, 30 Mar 2024 10:44:11 +0300 Subject: [PATCH 454/457] Fix for turrets (#493) - Turrets no more fall into murderous rage when owner locks or hurts them Co-authored-by: Dolzhukov, Viktor --- .../Zones/NpcSystem/SmartCreature.cs | 11 +++++++++- .../Zones/RemoteControl/CombatDrone.cs | 2 +- .../Zones/RemoteControl/SentryTurret.cs | 20 +++++++------------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs index 279e3c426..37026158d 100644 --- a/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs +++ b/src/Perpetuum/Zones/NpcSystem/SmartCreature.cs @@ -364,6 +364,11 @@ public override void AcceptVisitor(IEntityVisitor visitor) } } + public virtual bool IsFriendly(Unit source) + { + return false; + } + protected override void OnTileChanged() { base.OnTileChanged(); @@ -417,7 +422,11 @@ protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) } BossInfo?.OnDamageTaken(this, player); - AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); + + if (!IsFriendly(source)) + { + AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); + } } protected override void OnUpdate(TimeSpan time) diff --git a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs index f4585b94e..af07e3f17 100644 --- a/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs +++ b/src/Perpetuum/Zones/RemoteControl/CombatDrone.cs @@ -106,7 +106,7 @@ internal override bool IsHostile(MobileTeleport teleport) protected override void OnUnitLockStateChanged(Lock @lock) { - // Do nothing + // Do nothing; Combat Drones don't care about locks } protected override void UpdateUnitVisibility(Unit target) diff --git a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs index 6461f2fa2..a183503dd 100644 --- a/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs +++ b/src/Perpetuum/Zones/RemoteControl/SentryTurret.cs @@ -2,9 +2,8 @@ using Perpetuum.Players; using Perpetuum.Services.Standing; using Perpetuum.Units; -using Perpetuum.Zones.DamageProcessors; +using Perpetuum.Zones.Locking.Locks; using Perpetuum.Zones.NpcSystem; -using Perpetuum.Zones.NpcSystem.ThreatManaging; using System; namespace Perpetuum.Zones.RemoteControl @@ -73,21 +72,16 @@ protected override void OnUpdate(TimeSpan time) base.OnUpdate(time); } - protected override void OnDamageTaken(Unit source, DamageTakenEventArgs e) + protected override void OnUnitLockStateChanged(Lock @lock) { - base.OnDamageTaken(source, e); + // Do nothing; Sentry Turrets don't care about locks + } + public override bool IsFriendly(Unit source) + { Player player = Zone.ToPlayerOrGetOwnerPlayer(source); - if (player == null) - { - return; - } - - if (IsHostilePlayer(player)) - { - AddThreat(player, new Threat(ThreatType.Damage, e.TotalDamage * 0.9), true); - } + return player != null && IsHostilePlayer(player); } } } \ No newline at end of file From bf431d36beb1176bf8733ec550a8112982e24c15 Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Sun, 31 Mar 2024 14:17:00 +0300 Subject: [PATCH 455/457] Fix OpenPerpetuum/PerpetuumServer#342 (#488) * Update CombatAI.cs - when stopping npc, reset movement to null; - periodically check primary hostile. * Update WaypointMovement.cs - velocity vector in the case of zero length is calculated in the direction of the unit; - if at the start already at destination, then set Arrived to true. --- .../Zones/Movements/WaypointMovement.cs | 19 ++++++++++++++++--- src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs | 16 +++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) 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/CombatAI.cs b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs index 4b5b9eb07..8b2944c1b 100644 --- a/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs +++ b/src/Perpetuum/Zones/NpcSystem/AI/CombatAI.cs @@ -24,6 +24,8 @@ 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; @@ -261,7 +263,17 @@ protected void UpdateHostile(TimeSpan time, bool moveThreatToPseudoThreat = true return; } - if (!mostHated.Unit.CurrentPosition.IsEqual2D(this.lastTargetPosition)) + 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; @@ -393,6 +405,8 @@ private List FindNewAttackPosition(Unit hostile, CancellationToken cancel 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); From e0d2027cec33b89b250669426ed21ce3866674ee Mon Sep 17 00:00:00 2001 From: AqpAnaciy <101519462+aqpanaciy@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:43:13 +0300 Subject: [PATCH 456/457] Remove NuGet.exe (#495) * Update NuGet.targets - remove NuGet.exe . * Update .gitignore - add nuget.exe . --- .gitignore | 1 + .nuget/NuGet.exe | Bin 650752 -> 0 bytes .nuget/NuGet.targets | 8 ++++---- 3 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 .nuget/NuGet.exe diff --git a/.gitignore b/.gitignore index 6740e4f0a..d58682c18 100644 --- a/.gitignore +++ b/.gitignore @@ -259,3 +259,4 @@ paket-files/ __pycache__/ *.pyc /PerpetuumTests +.nuget/nuget.exe diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe deleted file mode 100644 index 8d13fd88c34af32c2180a1139f080c39e1d212d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650752 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 a|M&3!&k_jmjltYCrw=Co{rCS}3H%S*XXNMr 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; } From c7d91dc6b5d7d80e26267d3dabd6594be20ba9c5 Mon Sep 17 00:00:00 2001 From: "Dolzhukov, Viktor" Date: Fri, 10 May 2024 09:51:06 +0500 Subject: [PATCH 457/457] This fix should deal with the issue when it's possible to shoot non-flagged players on Alphas if attacker is flagged --- src/Perpetuum/Modules/ActiveModule.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Perpetuum/Modules/ActiveModule.cs b/src/Perpetuum/Modules/ActiveModule.cs index 360f218d3..fe02e47f1 100644 --- a/src/Perpetuum/Modules/ActiveModule.cs +++ b/src/Perpetuum/Modules/ActiveModule.cs @@ -176,10 +176,18 @@ public override Dictionary ToDictionary() protected virtual void HandleOffensivePVPCheck(Player parentPlayer, UnitLock unitLockTarget) { - if (parentPlayer != null && (unitLockTarget.Target as RemoteControlledCreature)?.CommandRobot is Player player) + Player targetPlayer = unitLockTarget.Target as Player; + if (targetPlayer is null && + ParentRobot is RemoteControlledCreature remoteControlledCreature && + remoteControlledCreature.CommandRobot is Player player) + { + parentPlayer = player; + } + + if (parentPlayer != null && targetPlayer != null) { _ = ((unitLockTarget.Target as Player)?.CheckPvp().ThrowIfError()); - _ = player.CheckPvp().ThrowIfError(); + _ = targetPlayer.CheckPvp().ThrowIfError(); } } @@ -215,6 +223,7 @@ ParentRobot is RemoteControlledCreature remoteControlledCreature && } else { + ; if (currentLock is TerrainLock terrainLockTarget) { IsInRange(terrainLockTarget.Location).ThrowIfFalse(ErrorCodes.TargetOutOfRange);