forked from SpaceStation14-Shenanigans/Monolith
Compare commits
287 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa8c6622b8 | |||
| bcc3402ef7 | |||
| a8a3cec8e4 | |||
| e19e64b903 | |||
| 5b6542720c | |||
| db7066605a | |||
| e14c8c0a8f | |||
| c061eaf610 | |||
| e56c2c5272 | |||
| 238d77ead3 | |||
| dea4d76981 | |||
| 82b46194d1 | |||
| e6835a4934 | |||
| 3d6597526b | |||
| 8894d4e5ff | |||
| eef28959e2 | |||
| 4ea83d389a | |||
| 02f74c26e9 | |||
| c0ea5adf5d | |||
| 6daf9f02e6 | |||
| afabe747f2 | |||
| 699232f653 | |||
| 262aa8cd4b | |||
| bcb9440046 | |||
| 93bd8b8356 | |||
| d14dd88149 | |||
| f520ddf911 | |||
| c2ea1eaf94 | |||
| fc105b61fc | |||
| 271c4e7efc | |||
| a4138f4b11 | |||
| ff74cf3ae1 | |||
| 663425a050 | |||
| b4e4a284bc | |||
| 3a8ac92ef8 | |||
| d3d3ab531e | |||
| 71cf5f42c7 | |||
| fce9e95910 | |||
| ee337596d6 | |||
| a2b3ddecdf | |||
| 0939533dc1 | |||
| 42f6f53be9 | |||
| 2651c00cfd | |||
| a462d09fb9 | |||
| ce617c185e | |||
| 131c5e79a5 | |||
| 86c9a843b1 | |||
| dd910f1550 | |||
| cd8d59c095 | |||
| 20bc7af2a4 | |||
| 65ea89b673 | |||
| 44445f3d2a | |||
| 648cc99aff | |||
| e8c2b72aa0 | |||
| 7d85dbca00 | |||
| 85e0cb1c76 | |||
| 3c32d2f6f5 | |||
| 9d8d0b9016 | |||
| 02e72a4c1d | |||
| 5fac554802 | |||
| bc62bf3700 | |||
| accb1404b0 | |||
| 326440aa22 | |||
| b4fe4ab5e7 | |||
| 8ff52fcd7b | |||
| e8a620e044 | |||
| 9ea80d6405 | |||
| abec847ca3 | |||
| 36214087e9 | |||
| 2b4f4f2ba0 | |||
| 258bc12f16 | |||
| d73cf7aeaf | |||
| b1e200408d | |||
| 95227750d9 | |||
| 34f71b59b9 | |||
| 547420d4bc | |||
| 510434fb65 | |||
| 862ce8352c | |||
| 552ab5718a | |||
| 2bacec206b | |||
| bd8ae0ec9b | |||
| 6ba17c6e1b | |||
| 50d7de7818 | |||
| 02348e780e | |||
| 80cc460d69 | |||
| e95a87a1d8 | |||
| 78ecd13c0a | |||
| 9eb4c8333c | |||
| 882b1acae7 | |||
| df39f7bd6d | |||
| c68d329dd2 | |||
| 5a2341407b | |||
| b4b49016d9 | |||
| 53a40c6c3c | |||
| fbf93d50be | |||
| 68144857e8 | |||
| 820a70c695 | |||
| bff7436490 | |||
| 2b76b0a55a | |||
| fa61e959fb | |||
| b427f087b0 | |||
| 7a8b90339f | |||
| cecc52bff9 | |||
| 378592a0ba | |||
| 89dff2ec03 | |||
| 4faad89b0c | |||
| 8f8b94862a | |||
| 7c29b33d27 | |||
| 8180e5d436 | |||
| e081dd59c6 | |||
| 295ef88a95 | |||
| 39292553b3 | |||
| 2652423c4d | |||
| fb836a0ee7 | |||
| 7a63aba489 | |||
| 7ec10d578c | |||
| 91f4eb7b51 | |||
| e552e2498f | |||
| ef34f255bf | |||
| 0f3390aba8 | |||
| 25033ae9c1 | |||
| d32fd9dd0a | |||
| 728c10974e | |||
| 50c5891b4e | |||
| 51818f6dc3 | |||
| dcaefa16d7 | |||
| 15936634b1 | |||
| 574bb25522 | |||
| 1125715a3f | |||
| 9e3a270988 | |||
| bf1a02bfcb | |||
| 57c59e3684 | |||
| 0e31d3885f | |||
| 27b05a3b75 | |||
| f99e36e101 | |||
| 5a17e7781f | |||
| 98f8d334a1 | |||
| c9bba3367d | |||
| 4ac8f937fb | |||
| 9648f54fc3 | |||
| 33e0bf8293 | |||
| 81ed57e9fb | |||
| 0943abba3e | |||
| 886752cd7a | |||
| f8ba24d3f1 | |||
| 1765c7d506 | |||
| 16f40a5289 | |||
| b80fb6d554 | |||
| e1932935fe | |||
| 5aa06e8fc0 | |||
| 914c5529ec | |||
| f406be9a5d | |||
| 002ba09258 | |||
| f89e18df88 | |||
| a7dcf1afea | |||
| 670785fd48 | |||
| 654a657788 | |||
| 52137b967a | |||
| 8e0bff68cb | |||
| af99be86ed | |||
| 3ed4f7cc1a | |||
| e72a06c19d | |||
| 6619538fb9 | |||
| 005950f2a0 | |||
| 468be1435c | |||
| 685422209e | |||
| ae58c4d021 | |||
| 464975916d | |||
| 2f39b9edce | |||
| a18831a0c5 | |||
| 42dcb56e22 | |||
| 3eeb83a16e | |||
| abf8bd7218 | |||
| c6de815de4 | |||
| 53b02b5830 | |||
| 40d2f70e08 | |||
| 1791e631f7 | |||
| f79b69a3f6 | |||
| b8d5ca7022 | |||
| f37ef8c994 | |||
| b4ae011b87 | |||
| 0adcb3c8a4 | |||
| ad1a263596 | |||
| 591a6ba333 | |||
| ebf2e56159 | |||
| c6b36bf126 | |||
| 2cd30797ac | |||
| 2aad60a3d7 | |||
| f561fcb45e | |||
| a68c0d48ba | |||
| ec2c5a521d | |||
| 39efe6f747 | |||
| 0816b4457c | |||
| 6c87da3dc3 | |||
| 05d4b2eebd | |||
| 6ee1e7d8f3 | |||
| f097148d99 | |||
| ef2ce6565d | |||
| 98ad67497b | |||
| 67644068b6 | |||
| 1b64380dbb | |||
| 465006f16b | |||
| e1ec12fb3e | |||
| d62e3a42d0 | |||
| 02be1ef282 | |||
| 85c4a0c9ec | |||
| baeb53ee82 | |||
| 933dc75c33 | |||
| fc45f016ee | |||
| 6a719f7c39 | |||
| d83db373fe | |||
| ccec7894c6 | |||
| 3319b5f0d3 | |||
| 81d9ae37e3 | |||
| 1990f2a272 | |||
| 3665639420 | |||
| 20e2ceb0db | |||
| 84a116fd27 | |||
| c3c402d380 | |||
| 92e8677e82 | |||
| 65365135e3 | |||
| 62e372c9d2 | |||
| 4279c1977d | |||
| 6406a7cf4f | |||
| 806e22e8b4 | |||
| c2934d0bd7 | |||
| 5316f1fdd9 | |||
| 66fed8d2a2 | |||
| 28e3584a67 | |||
| 78c94ddbc4 | |||
| cef95a03bf | |||
| bd3a35ee64 | |||
| 0b50c919e4 | |||
| afcf994545 | |||
| 5aaee26a09 | |||
| fb3c49bd09 | |||
| 2c381cbcd0 | |||
| f4e3ac2478 | |||
| 31de4af5e2 | |||
| 5e67e63032 | |||
| f3919fafc1 | |||
| 974b78863b | |||
| 1654bd5cf8 | |||
| d353667f97 | |||
| 47116d8ba7 | |||
| 9bd6d5f3f2 | |||
| 1dc696deb6 | |||
| f2df23dc81 | |||
| 76d6ea96e0 | |||
| 93ba265e00 | |||
| 16f73ec031 | |||
| 7a0f0d04c5 | |||
| bcce8e67a3 | |||
| 5afcdd9379 | |||
| 01c7ea464a | |||
| 4df38cc9cc | |||
| 8c0d2a50df | |||
| 2d8105de13 | |||
| 01bb73bf56 | |||
| f11c83a96e | |||
| 36d1740829 | |||
| c7a34dfc22 | |||
| e366c4d7cc | |||
| 5d5f6b4654 | |||
| 165f9950b5 | |||
| ef9858936d | |||
| d0a95f5d8b | |||
| ee386b0633 | |||
| de38cc18ef | |||
| ca928bdc83 | |||
| cee6ce9566 | |||
| 984a2c9b71 | |||
| 548e9eccb6 | |||
| cd817abd2c | |||
| f276df8fe9 | |||
| dd323be01d | |||
| 0cf9b35f08 | |||
| 351b225102 | |||
| 496432881e | |||
| 5798b57788 | |||
| ec88162ac0 | |||
| 3992aa846f | |||
| 4b62f35f10 | |||
| c5241854ce | |||
| c3f687a493 | |||
| 7b00d623e0 | |||
| dafbb5f3d8 |
@@ -10,18 +10,18 @@ jobs:
|
||||
if: ${{github.head_ref == 'master' || github.head_ref == 'main' || github.head_ref == 'develop'}}
|
||||
|
||||
steps:
|
||||
# - uses: superbrothers/close-pull-request@v3
|
||||
# with:
|
||||
# comment: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: "Thank you for contributing to the Monolith repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
|
||||
|
||||
# If you prefer to just comment on the pr and not close it, uncomment the bellow and comment the above
|
||||
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: ${{ github.event.number }},
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch. \n\n This pr won't be automatically closed. However, a maintainer may close it for this reason."
|
||||
})
|
||||
#- uses: actions/github-script@v7
|
||||
# with:
|
||||
# script: |
|
||||
# github.rest.issues.createComment({
|
||||
# issue_number: ${{ github.event.number }},
|
||||
# owner: context.repo.owner,
|
||||
# repo: context.repo.repo,
|
||||
# body: "Thank you for contributing to the Monolith repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name>` and resetting the master branch. \n\n This pr won't be automatically closed. However, a maintainer may close it for this reason."
|
||||
# })
|
||||
|
||||
@@ -38,6 +38,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Client._NF.Emp.Overlays; // Frontier
|
||||
using Content.Client._Mono.Company; // Mono
|
||||
|
||||
namespace Content.Client.Entry
|
||||
{
|
||||
@@ -75,6 +76,7 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
|
||||
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly CompanyManager _companyManager = default!; // Mono
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
@@ -137,6 +139,7 @@ namespace Content.Client.Entry
|
||||
_extendedDisconnectInformation.Initialize();
|
||||
_jobRequirements.Initialize();
|
||||
_playbackMan.Initialize();
|
||||
_companyManager.Initialize();
|
||||
|
||||
//AUTOSCALING default Setup!
|
||||
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);
|
||||
|
||||
@@ -24,6 +24,7 @@ using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Content.Client._Mono.Company; // Mono
|
||||
|
||||
namespace Content.Client.IoC
|
||||
{
|
||||
@@ -60,6 +61,7 @@ namespace Content.Client.IoC
|
||||
collection.Register<PlayerRateLimitManager>();
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
collection.Register<TitleWindowManager>();
|
||||
collection.Register<CompanyManager>(); // Mono
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_appearance.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, args.Component))
|
||||
if (!_appearance.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, args.Component))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client._Mono.Company; // Mono
|
||||
using Content.Client._Mono.MonoCoins;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
@@ -41,6 +42,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly JobRequirementsManager _requirements = default!;
|
||||
[Dependency] private readonly MarkingManager _markings = default!;
|
||||
[Dependency] private readonly CompanyManager _companyManager = default!; // Mono
|
||||
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
@@ -350,7 +352,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_prototypeManager,
|
||||
_resourceCache,
|
||||
_requirements,
|
||||
_markings);
|
||||
_markings,
|
||||
_companyManager); // Mono
|
||||
|
||||
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Content.Client._Mono.Company;
|
||||
|
||||
namespace Content.Client.Lobby.UI
|
||||
{
|
||||
@@ -52,6 +53,7 @@ namespace Content.Client.Lobby.UI
|
||||
private readonly JobRequirementsManager _requirements;
|
||||
private readonly LobbyUIController _controller;
|
||||
private readonly EntityWhitelistSystem _whitelist; // Frontier
|
||||
private readonly CompanyManager _companyManager; // Mono
|
||||
|
||||
private FlavorText.FlavorText? _flavorText;
|
||||
private TextEdit? _flavorTextEdit;
|
||||
@@ -116,7 +118,8 @@ namespace Content.Client.Lobby.UI
|
||||
IPrototypeManager prototypeManager,
|
||||
IResourceManager resManager,
|
||||
JobRequirementsManager requirements,
|
||||
MarkingManager markings)
|
||||
MarkingManager markings,
|
||||
CompanyManager manager) // Mono
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_sawmill = logManager.GetSawmill("profile.editor");
|
||||
@@ -127,6 +130,7 @@ namespace Content.Client.Lobby.UI
|
||||
_prototypeManager = prototypeManager;
|
||||
_markingManager = markings;
|
||||
_preferencesManager = preferencesManager;
|
||||
_companyManager = manager; // Mono
|
||||
_resManager = resManager;
|
||||
_requirements = requirements;
|
||||
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
@@ -446,12 +450,9 @@ namespace Content.Client.Lobby.UI
|
||||
// Clear any existing items
|
||||
CompanyButton.Clear();
|
||||
|
||||
var username = _playerManager.LocalPlayer?.Session?.Name; //Lua modified - company login support
|
||||
|
||||
// Add all companies from prototypes - use consistent sorting with UpdateCompanyControls
|
||||
var companies = _prototypeManager.EnumeratePrototypes<CompanyPrototype>()
|
||||
//.Where(c => !c.Disabled) // Filter out disabled companies
|
||||
.Where(c => !c.Disabled || (username != null && c.Logins.Contains(username))) //Lua modified - company login support
|
||||
.Where(c => !c.Disabled && _companyManager.IsAllowed(c.ID))
|
||||
.ToList();
|
||||
companies.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
@@ -2194,11 +2195,8 @@ namespace Content.Client.Lobby.UI
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
var username = _playerManager.LocalPlayer?.Session?.Name; //Lua modified - company login support
|
||||
|
||||
var companies = _prototypeManager.EnumeratePrototypes<CompanyPrototype>()
|
||||
//.Where(c => !c.Disabled) // Filter out disabled companies
|
||||
.Where(c => !c.Disabled || (username != null && c.Logins.Contains(username))) //Lua modified - company login support
|
||||
.Where(c => !c.Disabled && _companyManager.IsAllowed(c.ID))
|
||||
.ToList();
|
||||
companies.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
|
||||
@@ -77,6 +77,16 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
||||
if (_selected is {} selected && !_cyborgs.ContainsKey(selected))
|
||||
_selected = null;
|
||||
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
var isAiControllable = false;
|
||||
|
||||
if (_selected != null)
|
||||
{
|
||||
_cyborgs.TryGetValue(_selected, out var data);
|
||||
isAiControllable = data.IsAiControllable;
|
||||
}
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
|
||||
var hasCyborgs = _cyborgs.Count > 0;
|
||||
NoCyborgs.Visible = !hasCyborgs;
|
||||
CyborgsContainer.Visible = hasCyborgs;
|
||||
|
||||
@@ -138,6 +138,293 @@ public partial class BaseShuttleControl : MapGridControl
|
||||
}
|
||||
// End Frontier Corvax
|
||||
|
||||
// Mono
|
||||
protected void DrawAzimuthScale(DrawingHandleScreen handle, Angle rotation)
|
||||
{
|
||||
const int azimuthMinorTickStepDegrees = 10;
|
||||
const int azimuthMajorTickInterval = 3;
|
||||
const int azimuthCardinalTickInterval = 9;
|
||||
const float edgeInset = 10f;
|
||||
const float tickInset = 4f;
|
||||
const float minorTickLength = 5f;
|
||||
const float majorTickLength = 9f;
|
||||
const float cardinalTickLength = 12f;
|
||||
const float labelPadding = 3f;
|
||||
const float labelOffset = tickInset + cardinalTickLength + labelPadding;
|
||||
const float regularLabelScale = 0.62f;
|
||||
const float cardinalLabelScale = 0.72f;
|
||||
|
||||
var origin = MidPointVector;
|
||||
var viewportSize = new Vector2(PixelWidth, PixelHeight);
|
||||
|
||||
var subtleGray = Color.FromHex("#676767");
|
||||
var northAccent = Color.FromHex("#7F7442");
|
||||
var eastAccent = Color.FromHex("#456273");
|
||||
var baseTickColor = subtleGray.WithAlpha(0.34f);
|
||||
var majorTickColor = subtleGray.WithAlpha(0.5f);
|
||||
var cardinalTickColor = subtleGray.WithAlpha(0.68f);
|
||||
var regularLabelColor = subtleGray.WithAlpha(0.76f);
|
||||
var cardinalLabelColor = subtleGray.WithAlpha(0.9f);
|
||||
var labelShadowColor = Color.Black.WithAlpha(0.36f);
|
||||
|
||||
for (var step = 0; step < 360 / azimuthMinorTickStepDegrees; step++)
|
||||
{
|
||||
var degrees = step * azimuthMinorTickStepDegrees;
|
||||
var direction = GetAzimuthDirection(rotation, degrees);
|
||||
var isCardinal = step % azimuthCardinalTickInterval == 0;
|
||||
var isMajor = step % azimuthMajorTickInterval == 0;
|
||||
var isNorth = degrees == 0;
|
||||
var isEast = degrees == 90;
|
||||
var tickLength = isCardinal
|
||||
? cardinalTickLength
|
||||
: isMajor
|
||||
? majorTickLength
|
||||
: minorTickLength;
|
||||
|
||||
if (!TryGetViewportEdgePoint(origin, direction, viewportSize, edgeInset, out var edgePoint, out var inwardNormal))
|
||||
continue;
|
||||
|
||||
var outer = edgePoint + inwardNormal * tickInset;
|
||||
var inner = outer + inwardNormal * tickLength;
|
||||
var tickColor = isNorth
|
||||
? northAccent.WithAlpha(0.58f)
|
||||
: isEast
|
||||
? eastAccent.WithAlpha(0.58f)
|
||||
: isCardinal
|
||||
? cardinalTickColor
|
||||
: isMajor
|
||||
? majorTickColor
|
||||
: baseTickColor;
|
||||
handle.DrawLine(outer, inner, tickColor);
|
||||
if (isNorth)
|
||||
{
|
||||
var sideOffset = new Vector2(-inwardNormal.Y, inwardNormal.X) * 0.35f;
|
||||
handle.DrawLine(outer + sideOffset, inner + sideOffset, tickColor.WithAlpha(0.46f));
|
||||
}
|
||||
|
||||
if (!isMajor)
|
||||
continue;
|
||||
|
||||
var label = degrees.ToString();
|
||||
var labelScale = isCardinal ? cardinalLabelScale : regularLabelScale;
|
||||
var labelColor = isEast
|
||||
? eastAccent.WithAlpha(0.76f)
|
||||
: isCardinal
|
||||
? cardinalLabelColor
|
||||
: regularLabelColor;
|
||||
var labelSize = handle.GetDimensions(Font, label, labelScale);
|
||||
var labelPosition = GetEdgeLabelPosition(edgePoint, inwardNormal, labelSize, viewportSize, labelOffset);
|
||||
handle.DrawString(Font, labelPosition + new Vector2(1f, 1f), label, labelScale, labelShadowColor);
|
||||
handle.DrawString(Font, labelPosition, label, labelScale, labelColor);
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawAzimuthRing(DrawingHandleScreen handle, Angle rotation)
|
||||
{
|
||||
const int azimuthMinorTickStepDegrees = 10;
|
||||
const int azimuthMajorTickInterval = 3;
|
||||
const int azimuthCardinalTickInterval = 9;
|
||||
const float ringInset = 16f;
|
||||
const float minorTickLength = 5f;
|
||||
const float majorTickLength = 9f;
|
||||
const float cardinalTickLength = 12f;
|
||||
const float labelPadding = 5f;
|
||||
const float regularLabelScale = 0.62f;
|
||||
const float cardinalLabelScale = 0.72f;
|
||||
|
||||
var origin = MidPointVector;
|
||||
var radius = MathF.Max(8f, SizeFull * 0.5f - ringInset);
|
||||
|
||||
var subtleGray = Color.FromHex("#676767");
|
||||
var northAccent = Color.FromHex("#7F7442");
|
||||
var eastAccent = Color.FromHex("#456273");
|
||||
var baseTickColor = subtleGray.WithAlpha(0.34f);
|
||||
var majorTickColor = subtleGray.WithAlpha(0.5f);
|
||||
var cardinalTickColor = subtleGray.WithAlpha(0.68f);
|
||||
var regularLabelColor = subtleGray.WithAlpha(0.76f);
|
||||
var cardinalLabelColor = subtleGray.WithAlpha(0.9f);
|
||||
var labelShadowColor = Color.Black.WithAlpha(0.36f);
|
||||
|
||||
handle.DrawCircle(origin, radius, subtleGray.WithAlpha(0.12f), false);
|
||||
|
||||
for (var step = 0; step < 360 / azimuthMinorTickStepDegrees; step++)
|
||||
{
|
||||
var degrees = step * azimuthMinorTickStepDegrees;
|
||||
var direction = GetAzimuthDirection(rotation, degrees);
|
||||
var isCardinal = step % azimuthCardinalTickInterval == 0;
|
||||
var isMajor = step % azimuthMajorTickInterval == 0;
|
||||
var isNorth = degrees == 0;
|
||||
var isEast = degrees == 90;
|
||||
var tickLength = isCardinal
|
||||
? cardinalTickLength
|
||||
: isMajor
|
||||
? majorTickLength
|
||||
: minorTickLength;
|
||||
|
||||
var outer = origin + direction * radius;
|
||||
var inner = origin + direction * (radius - tickLength);
|
||||
var tickColor = isNorth
|
||||
? northAccent.WithAlpha(0.58f)
|
||||
: isEast
|
||||
? eastAccent.WithAlpha(0.58f)
|
||||
: isCardinal
|
||||
? cardinalTickColor
|
||||
: isMajor
|
||||
? majorTickColor
|
||||
: baseTickColor;
|
||||
handle.DrawLine(outer, inner, tickColor);
|
||||
if (isNorth)
|
||||
{
|
||||
var sideOffset = new Vector2(-direction.Y, direction.X) * 0.35f;
|
||||
handle.DrawLine(outer + sideOffset, inner + sideOffset, tickColor.WithAlpha(0.46f));
|
||||
}
|
||||
|
||||
if (!isMajor)
|
||||
continue;
|
||||
|
||||
var label = degrees.ToString();
|
||||
var labelScale = isCardinal ? cardinalLabelScale : regularLabelScale;
|
||||
var labelColor = isEast
|
||||
? eastAccent.WithAlpha(0.76f)
|
||||
: isCardinal
|
||||
? cardinalLabelColor
|
||||
: regularLabelColor;
|
||||
var labelSize = handle.GetDimensions(Font, label, labelScale);
|
||||
var labelPosition = origin + direction * (radius - tickLength - labelPadding) - labelSize * 0.5f;
|
||||
handle.DrawString(Font, labelPosition + new Vector2(1f, 1f), label, labelScale, labelShadowColor);
|
||||
handle.DrawString(Font, labelPosition, label, labelScale, labelColor);
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawCompassOverlay(DrawingHandleScreen handle, Angle heading)
|
||||
{
|
||||
var compassCenter = new Vector2(44f, 44f);
|
||||
const float compassRadius = 24f;
|
||||
var ringColor = Color.White.WithAlpha(0.08f);
|
||||
var northColor = Color.FromHex("#776C43");
|
||||
var eastColor = Color.FromHex("#4C6572");
|
||||
handle.DrawCircle(compassCenter, compassRadius, ringColor, false);
|
||||
handle.DrawCircle(compassCenter, 2.5f, ringColor.WithAlpha(0.48f), true);
|
||||
|
||||
DrawCompassNeedle(handle, compassCenter, compassRadius, GetAzimuthDirection(heading, 0f), "N", northColor);
|
||||
DrawCompassNeedle(handle, compassCenter, compassRadius * 0.82f, GetAzimuthDirection(heading, 90f), "E", eastColor);
|
||||
}
|
||||
|
||||
private void DrawCompassNeedle(
|
||||
DrawingHandleScreen handle,
|
||||
Vector2 compassCenter,
|
||||
float radius,
|
||||
Vector2 direction,
|
||||
string label,
|
||||
Color color)
|
||||
{
|
||||
var tip = compassCenter + direction * radius;
|
||||
var tail = compassCenter - direction * (radius * 0.35f);
|
||||
DrawArrowLine(handle, tail, tip, color.WithAlpha(0.58f), 6f);
|
||||
|
||||
var labelSize = handle.GetDimensions(Font, label, 0.9f);
|
||||
var labelPosition = tip + direction * 6f - new Vector2(labelSize.X * 0.5f, labelSize.Y * 0.5f);
|
||||
handle.DrawString(Font, labelPosition, label, 0.9f, color.WithAlpha(0.7f));
|
||||
}
|
||||
|
||||
private static void DrawArrowLine(
|
||||
DrawingHandleScreen handle,
|
||||
Vector2 start,
|
||||
Vector2 end,
|
||||
Color color,
|
||||
float headLength)
|
||||
{
|
||||
var direction = end - start;
|
||||
if (direction.LengthSquared() <= 0.001f)
|
||||
return;
|
||||
|
||||
direction = Vector2.Normalize(direction);
|
||||
var left = new Vector2(-direction.Y, direction.X);
|
||||
handle.DrawLine(start, end, color);
|
||||
handle.DrawLine(end, end - direction * headLength + left * (headLength * 0.45f), color);
|
||||
handle.DrawLine(end, end - direction * headLength - left * (headLength * 0.45f), color);
|
||||
}
|
||||
|
||||
private static bool TryGetViewportEdgePoint(
|
||||
Vector2 origin,
|
||||
Vector2 direction,
|
||||
Vector2 viewportSize,
|
||||
float inset,
|
||||
out Vector2 edgePoint,
|
||||
out Vector2 inwardNormal)
|
||||
{
|
||||
edgePoint = default;
|
||||
inwardNormal = default;
|
||||
|
||||
if (direction.LengthSquared() <= 0.001f)
|
||||
return false;
|
||||
|
||||
direction = Vector2.Normalize(direction);
|
||||
var min = new Vector2(inset, inset);
|
||||
var max = viewportSize - min;
|
||||
if (max.X <= min.X || max.Y <= min.Y)
|
||||
return false;
|
||||
|
||||
var tx = direction.X > 0f
|
||||
? (max.X - origin.X) / direction.X
|
||||
: direction.X < 0f
|
||||
? (min.X - origin.X) / direction.X
|
||||
: float.PositiveInfinity;
|
||||
var ty = direction.Y > 0f
|
||||
? (max.Y - origin.Y) / direction.Y
|
||||
: direction.Y < 0f
|
||||
? (min.Y - origin.Y) / direction.Y
|
||||
: float.PositiveInfinity;
|
||||
|
||||
var t = Math.Min(tx, ty);
|
||||
if (!float.IsFinite(t) || t <= 0f)
|
||||
return false;
|
||||
|
||||
edgePoint = origin + direction * t;
|
||||
|
||||
if (tx < ty)
|
||||
inwardNormal = direction.X > 0f ? -Vector2.UnitX : Vector2.UnitX;
|
||||
else
|
||||
inwardNormal = direction.Y > 0f ? -Vector2.UnitY : Vector2.UnitY;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Vector2 GetEdgeLabelPosition(
|
||||
Vector2 edgePoint,
|
||||
Vector2 inwardNormal,
|
||||
Vector2 labelSize,
|
||||
Vector2 viewportSize,
|
||||
float offset)
|
||||
{
|
||||
var anchor = edgePoint + inwardNormal * offset;
|
||||
Vector2 requested;
|
||||
if (Math.Abs(inwardNormal.X) > Math.Abs(inwardNormal.Y))
|
||||
{
|
||||
requested = inwardNormal.X > 0f
|
||||
? new Vector2(anchor.X, anchor.Y - labelSize.Y * 0.5f)
|
||||
: new Vector2(anchor.X - labelSize.X, anchor.Y - labelSize.Y * 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
requested = inwardNormal.Y > 0f
|
||||
? new Vector2(anchor.X - labelSize.X * 0.5f, anchor.Y)
|
||||
: new Vector2(anchor.X - labelSize.X * 0.5f, anchor.Y - labelSize.Y);
|
||||
}
|
||||
|
||||
var minPosition = new Vector2(offset, offset);
|
||||
var maxPosition = viewportSize - labelSize - minPosition;
|
||||
return Vector2.Clamp(requested, minPosition, maxPosition);
|
||||
}
|
||||
|
||||
private static Vector2 GetAzimuthDirection(Angle baseAngle, float azimuthDegrees)
|
||||
{
|
||||
var angle = baseAngle + Angle.FromDegrees(azimuthDegrees);
|
||||
var radians = (float) angle.Theta - MathF.PI / 2f;
|
||||
return new Vector2(MathF.Cos(radians), MathF.Sin(radians));
|
||||
}
|
||||
// End Mono
|
||||
|
||||
protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 gridToView, Entity<MapGridComponent> grid, Color color, float alpha = 0.01f)
|
||||
{
|
||||
var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
Orientation="Vertical">
|
||||
<!-- FTL Lock Section -->
|
||||
<controls1:StripeBack MinSize="48 48">
|
||||
<controls:Label Text="{controls:Loc 'FTL Lock'}" HorizontalAlignment="Center"/>
|
||||
<controls:Label Text="{controls:Loc 'shuttle-console-ftl-lock'}" HorizontalAlignment="Center"/>
|
||||
</controls1:StripeBack>
|
||||
<controls:PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="4 4 4 4">
|
||||
<controls:PanelContainer.PanelOverride>
|
||||
@@ -36,12 +36,12 @@
|
||||
</controls:PanelContainer.PanelOverride>
|
||||
<controls:BoxContainer Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="5 5 5 5">
|
||||
<controls:Button Name="FTLLockEnabledButton"
|
||||
Text="{controls:Loc 'Enabled'}"
|
||||
Text="{controls:Loc 'shuttle-console-ftl-lock-enabled'}"
|
||||
ToggleMode="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight"/>
|
||||
<controls:Button Name="FTLLockDisabledButton"
|
||||
Text="{controls:Loc 'Disabled'}"
|
||||
Text="{controls:Loc 'shuttle-console-ftl-lock-disabled'}"
|
||||
ToggleMode="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenLeft"/>
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<!-- Docks Section -->
|
||||
<controls1:StripeBack MinSize="48 48">
|
||||
<controls:Label Text="{controls:Loc 'Docks'}" HorizontalAlignment="Center"/>
|
||||
<controls:Label Text="{controls:Loc 'shuttle-console-docks-label'}" HorizontalAlignment="Center"/>
|
||||
</controls1:StripeBack>
|
||||
<controls:PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="4 4 4 4">
|
||||
<controls:PanelContainer.PanelOverride>
|
||||
@@ -62,7 +62,7 @@
|
||||
<graphics:StyleBoxFlat BorderThickness="2" BorderColor="#333333" BackgroundColor="#121212"/>
|
||||
</controls:PanelContainer.PanelOverride>
|
||||
<controls:Button Name="UndockAllButton"
|
||||
Text="{controls:Loc 'Undock All'}"
|
||||
Text="{controls:Loc 'shuttle-console-undock-all'}"
|
||||
Margin="5 5 5 5"
|
||||
StyleClasses="ButtonSquare"/>
|
||||
</controls:PanelContainer>
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
xmlns:ui="clr-namespace:Content.Client.Shuttles.UI"
|
||||
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<ui:ShuttleNavControl Name="NavRadar"
|
||||
MouseFilter="Stop"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="5 4 10 5"/>
|
||||
<!-- Mono - wrap -->
|
||||
<controls:LayoutContainer Margin="5 4 10 5">
|
||||
<ui:ShuttleNavControl Name="NavRadar"
|
||||
MouseFilter="Stop"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"/>
|
||||
</controls:LayoutContainer>
|
||||
<!-- Nav controls -->
|
||||
<controls:PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="4 4 4 4">
|
||||
<controls:PanelContainer.PanelOverride>
|
||||
@@ -173,11 +175,8 @@
|
||||
<!-- Frontier - Maximum Shuttle Speed -->
|
||||
<controls:BoxContainer Orientation="Vertical" HorizontalExpand="True" Name="MaximumShuttleSpeedBox" Margin="4 4 4 4">
|
||||
<controls:Label Text="{controls:Loc 'shuttle-console-maximum-speed'}"/>
|
||||
<controls:SliderIntInput Name="MaximumShuttleSpeedValue"
|
||||
<controls:LineEdit Name="MaximumShuttleSpeedValue"
|
||||
Access="Public"
|
||||
MinValue="0"
|
||||
MaxValue="140"
|
||||
Value="140"
|
||||
HorizontalExpand="True"/>
|
||||
</controls:BoxContainer>
|
||||
</controls:BoxContainer>
|
||||
@@ -215,36 +214,43 @@
|
||||
Text="{controls:Loc 'shuttle-console-device-button-1'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton2"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-2'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton3"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-3'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton4"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-4'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton5"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-5'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton6"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-6'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton7"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-7'}"
|
||||
TextAlign="Center"
|
||||
StyleClasses="ButtonSquare"
|
||||
ToolTip="{controls:Loc 'shuttle-console-network-connect-tooltip'}"
|
||||
SetWidth="55"/>
|
||||
<controls:Button Name="DeviceButton8"
|
||||
Text="{controls:Loc 'shuttle-console-device-button-8'}"
|
||||
|
||||
@@ -176,14 +176,15 @@ public sealed partial class NavScreen : BoxContainer
|
||||
var (_, worldRot, worldMatrix) = _xformSystem.GetWorldPositionRotationMatrix(gridXform);
|
||||
var worldPos = Vector2.Transform(gridBody.LocalCenter, worldMatrix);
|
||||
|
||||
// Get the positive reduced angle.
|
||||
var displayRot = -worldRot.Reduced();
|
||||
// Mono - remap to [0, 360)
|
||||
var displayRot = (-worldRot).Reduced();
|
||||
var displayRotDegrees = displayRot.FlipPositive().Degrees;
|
||||
|
||||
GridPosition.Text = Loc.GetString("shuttle-console-position-value",
|
||||
("X", $"{worldPos.X:0.0}"),
|
||||
("Y", $"{worldPos.Y:0.0}"));
|
||||
GridOrientation.Text = Loc.GetString("shuttle-console-orientation-value",
|
||||
("angle", $"{displayRot.Degrees:0.0}"));
|
||||
("angle", $"{displayRotDegrees:0.0}"));
|
||||
|
||||
var gridVelocity = gridBody.LinearVelocity;
|
||||
gridVelocity = displayRot.RotateVec(gridVelocity);
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
Title="{Loc 'radar-console-window-title'}"
|
||||
SetSize="648 672"
|
||||
MinSize="256 256">
|
||||
<ui:ShuttleNavControl Name="RadarScreen"
|
||||
Margin="4"
|
||||
HorizontalExpand = "True"
|
||||
VerticalExpand = "True"/>
|
||||
<LayoutContainer Margin="4">
|
||||
<!-- Mono - wrap -->
|
||||
<ui:ShuttleNavControl Name="RadarScreen"
|
||||
HorizontalExpand = "True"
|
||||
VerticalExpand = "True"/>
|
||||
</LayoutContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -13,6 +13,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
@@ -45,9 +46,10 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
/// <summary>
|
||||
/// Entity of controlling console
|
||||
/// </summary>
|
||||
private EntityUid? _consoleEntity;
|
||||
protected EntityUid? _consoleEntity; // Mono
|
||||
|
||||
protected Angle? _rotation; // Mono
|
||||
// Mono - is world rotation of the view.
|
||||
protected Angle? _rotation = Angle.Zero;
|
||||
|
||||
private Dictionary<NetEntity, List<DockingPortState>> _docks = new();
|
||||
|
||||
@@ -65,7 +67,6 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
public bool ShowIFF { get; set; } = true;
|
||||
public bool ShowIFFShuttles { get; set; } = true;
|
||||
public bool ShowDocks { get; set; } = true;
|
||||
public bool RotateWithEntity { get; set; } = true;
|
||||
|
||||
public float MaximumIFFDistance { get; set; } = 3000f; // Frontier // Mono - 3000 by default to not gigaclutter
|
||||
public bool HideCoords { get; set; } = false; // Frontier
|
||||
@@ -88,14 +89,16 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
public List<EntityUid>? Detectors = null;
|
||||
|
||||
#region Mono
|
||||
public bool RelativePanning = false;
|
||||
|
||||
// These 2 handle timing updates
|
||||
protected const float RadarUpdateInterval = 0f;
|
||||
protected float _updateAccumulator = 0f;
|
||||
|
||||
// doesn't support absolute panning and angle follow, but why would you need that?
|
||||
protected bool _relativePanning = false;
|
||||
protected bool _angleFollow = true;
|
||||
private bool _radarModeDefaultsApplied;
|
||||
|
||||
private bool _wasPanned = false;
|
||||
private EntityCoordinates? _oldCoordinates;
|
||||
#endregion
|
||||
|
||||
protected bool _isMouseDown;
|
||||
@@ -104,6 +107,20 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
protected float _lastFireTime;
|
||||
protected const float FireRateLimit = 0.1f; // 100ms between shots
|
||||
|
||||
private enum RadarAzimuthMode : byte
|
||||
{
|
||||
Edge,
|
||||
Ring,
|
||||
Off,
|
||||
}
|
||||
|
||||
private BoxContainer? _radarModeButtons;
|
||||
private RadarModeButton? _radarAzimuthButton;
|
||||
private RadarModeButton? _radarRotationButton;
|
||||
private RadarModeButton? _radarAnchorButton;
|
||||
private RadarModeButton? _radarResetButton;
|
||||
private RadarAzimuthMode _azimuthMode;
|
||||
|
||||
public ShuttleNavControl() : this(64f, 256f, 256f) { } // Mono
|
||||
|
||||
public ShuttleNavControl(float minRange, float maxRange, float range) : base(minRange, maxRange, range) // Mono
|
||||
@@ -135,12 +152,279 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
return;
|
||||
|
||||
_coordinates = coordinates;
|
||||
_rotation = angle;
|
||||
// Mono - TODO: either remove this or make this do anything
|
||||
// _rotation = angle;
|
||||
}
|
||||
|
||||
public void SetConsole(EntityUid? consoleEntity)
|
||||
{
|
||||
if (_consoleEntity == consoleEntity)
|
||||
return;
|
||||
|
||||
_consoleEntity = consoleEntity;
|
||||
_radarModeDefaultsApplied = false;
|
||||
ApplyRadarModeDefaults();
|
||||
}
|
||||
|
||||
// converts Offset to world-relative offset so we can modify radar parameters without conversion headaches
|
||||
// call this before potentially rotation-modifying operations and PanToEnt() after
|
||||
private void PanToWorld()
|
||||
{
|
||||
if (!_angleFollow || _coordinates is not { } cord)
|
||||
return;
|
||||
var coordEntRot = _transform.GetWorldRotation(cord.EntityId);
|
||||
Offset = coordEntRot.RotateVec(Offset);
|
||||
}
|
||||
|
||||
// converts world-relative Offset to relative to current view
|
||||
// call this after potentially rotation-modifying operations and PanToWorld() before
|
||||
private void PanToEnt()
|
||||
{
|
||||
if (!_angleFollow || _coordinates is not { } cord)
|
||||
return;
|
||||
var coordEntRot = _transform.GetWorldRotation(cord.EntityId);
|
||||
Offset = (-coordEntRot).RotateVec(Offset);
|
||||
}
|
||||
|
||||
// reanchor our view to console or grid
|
||||
private void ReanchorView()
|
||||
{
|
||||
if (_consoleEntity is not { } cons
|
||||
|| _coordinates is not { } cord
|
||||
|| !EntManager.TryGetComponent<TransformComponent>(cons, out var xform))
|
||||
return;
|
||||
|
||||
PanToWorld();
|
||||
var anchTo = cons;
|
||||
if (xform.GridUid != null)
|
||||
anchTo = xform.GridUid.Value;
|
||||
_coordinates = _transform.WithEntityId(cord, anchTo);
|
||||
_relativePanning = true;
|
||||
PanToEnt();
|
||||
}
|
||||
|
||||
private void EnsureRadarModeButtons()
|
||||
{
|
||||
if (_radarModeButtons != null)
|
||||
return;
|
||||
|
||||
if (Parent is not LayoutContainer parent)
|
||||
return;
|
||||
|
||||
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
_radarModeButtons = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 2,
|
||||
HorizontalExpand = false,
|
||||
VerticalExpand = false,
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
};
|
||||
|
||||
_radarAzimuthButton = CreateRadarModeButton(RadarModeButtonIcon.Azimuth);
|
||||
_radarAzimuthButton.ToolTip = Loc.GetString("shuttle-console-button-azimuth");
|
||||
_radarAzimuthButton.OnPressed += _ =>
|
||||
{
|
||||
_azimuthMode = _azimuthMode switch
|
||||
{
|
||||
RadarAzimuthMode.Edge => RadarAzimuthMode.Ring,
|
||||
RadarAzimuthMode.Ring => RadarAzimuthMode.Off,
|
||||
_ => RadarAzimuthMode.Edge,
|
||||
};
|
||||
};
|
||||
|
||||
_radarRotationButton = CreateRadarModeButton(RadarModeButtonIcon.Rotation);
|
||||
_radarRotationButton.ToolTip = Loc.GetString("shuttle-console-button-rotation");
|
||||
_radarRotationButton.OnPressed += _ =>
|
||||
{
|
||||
PanToWorld();
|
||||
_angleFollow ^= true;
|
||||
PanToEnt();
|
||||
};
|
||||
|
||||
_radarAnchorButton = CreateRadarModeButton(RadarModeButtonIcon.Anchor);
|
||||
_radarAnchorButton.ToolTip = Loc.GetString("shuttle-console-button-anchor");
|
||||
_radarAnchorButton.OnPressed += _ =>
|
||||
{
|
||||
if (_coordinates is not { } cord)
|
||||
return;
|
||||
|
||||
// if we're anchored or not yet bound to map, just deanchor
|
||||
if (_relativePanning || !EntManager.HasComponent<MapComponent>(cord.EntityId))
|
||||
{
|
||||
PanToWorld();
|
||||
// we're deanchoring, so simply detach our view to the map
|
||||
_coordinates = _transform.ToCoordinates(_transform.ToMapCoordinates(cord));
|
||||
_relativePanning = false;
|
||||
_wasPanned = true;
|
||||
PanToEnt();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReanchorView();
|
||||
}
|
||||
};
|
||||
|
||||
_radarResetButton = CreateRadarModeButton(RadarModeButtonIcon.Reset);
|
||||
_radarResetButton.ToolTip = Loc.GetString("shuttle-console-button-reset");
|
||||
_radarResetButton.OnPressed += _ =>
|
||||
{
|
||||
_radarModeDefaultsApplied = false;
|
||||
ApplyRadarModeDefaults();
|
||||
if (EntManager.TryGetComponent<TransformComponent>(_consoleEntity, out var xform))
|
||||
{
|
||||
Offset = Vector2.Zero;
|
||||
_coordinates = xform.Coordinates;
|
||||
ReanchorView();
|
||||
}
|
||||
};
|
||||
|
||||
_radarModeButtons.AddChild(_radarAzimuthButton);
|
||||
_radarModeButtons.AddChild(_radarRotationButton);
|
||||
_radarModeButtons.AddChild(_radarAnchorButton);
|
||||
_radarModeButtons.AddChild(_radarResetButton);
|
||||
|
||||
parent.AddChild(_radarModeButtons);
|
||||
LayoutContainer.SetAnchorAndMarginPreset(_radarModeButtons, LayoutContainer.LayoutPreset.BottomLeft, margin: 10);
|
||||
}
|
||||
|
||||
private void ApplyRadarModeDefaults()
|
||||
{
|
||||
if (_radarModeDefaultsApplied)
|
||||
return;
|
||||
|
||||
if (!EntManager.TryGetComponent<RadarConsoleComponent>(_consoleEntity, out var radar))
|
||||
return;
|
||||
|
||||
_radarModeDefaultsApplied = true;
|
||||
Draggable = radar.Pannable;
|
||||
_angleFollow = !radar.NoRotate;
|
||||
_relativePanning = radar.RelativePanning;
|
||||
}
|
||||
|
||||
private static RadarModeButton CreateRadarModeButton(RadarModeButtonIcon icon = RadarModeButtonIcon.None)
|
||||
{
|
||||
return new RadarModeButton(icon)
|
||||
{
|
||||
MinWidth = 16f,
|
||||
MaxWidth = 16f,
|
||||
MinHeight = 16f,
|
||||
MaxHeight = 16f,
|
||||
HorizontalExpand = false,
|
||||
VerticalExpand = false,
|
||||
ModulateSelfOverride = Color.White.WithAlpha(0.55f),
|
||||
};
|
||||
}
|
||||
|
||||
internal enum RadarModeButtonIcon : byte
|
||||
{
|
||||
None,
|
||||
Azimuth,
|
||||
Rotation,
|
||||
Anchor,
|
||||
Reset
|
||||
}
|
||||
|
||||
public sealed class RadarModeButton : BaseButton
|
||||
{
|
||||
private readonly RadarModeButtonIcon _icon;
|
||||
|
||||
internal RadarModeButton(RadarModeButtonIcon icon)
|
||||
{
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
return new Vector2(16f, 16f);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
var alpha = DrawMode switch
|
||||
{
|
||||
DrawModeEnum.Pressed => 0.62f,
|
||||
DrawModeEnum.Hover => 0.48f,
|
||||
_ => 0.32f,
|
||||
};
|
||||
|
||||
var center = new Vector2(PixelSize.X * 0.5f, PixelSize.Y * 0.5f);
|
||||
var radius = MathF.Min(PixelSize.X, PixelSize.Y) * 0.42f;
|
||||
handle.DrawCircle(center, radius, Color.White.WithAlpha(alpha));
|
||||
|
||||
var up = new Vector2(0f, -radius);
|
||||
var down = new Vector2(0f, radius);
|
||||
var leftOffs = new Vector2(-radius, 0f);
|
||||
var rightOffs = new Vector2(radius, 0f);
|
||||
var top = center + up;
|
||||
var bottom = center + down;
|
||||
var left = center + leftOffs;
|
||||
var right = center + rightOffs;
|
||||
|
||||
switch (_icon) {
|
||||
case RadarModeButtonIcon.Azimuth:
|
||||
{
|
||||
var iconColor = Color.Black.WithAlpha(0.95f);
|
||||
var topLeft = center + (up + leftOffs) / MathF.Sqrt(2f);
|
||||
var topRight = center + (up + rightOffs) / MathF.Sqrt(2f);
|
||||
var bottomLeft = center + (down + leftOffs) / MathF.Sqrt(2f);
|
||||
var bottomRight = center + (down + rightOffs) / MathF.Sqrt(2f);
|
||||
|
||||
handle.DrawLine(topLeft, bottomRight, iconColor);
|
||||
handle.DrawLine(bottomLeft, topRight, iconColor);
|
||||
handle.DrawLine(left, right, iconColor);
|
||||
handle.DrawLine(bottom, top, iconColor);
|
||||
break;
|
||||
}
|
||||
case RadarModeButtonIcon.Rotation:
|
||||
{
|
||||
var iconColor = Color.Yellow.WithAlpha(0.95f);
|
||||
var innerRatio = 0.8f;
|
||||
var segCount = 3;
|
||||
var prevOffs = up * innerRatio;
|
||||
for (var i = 0; i < segCount; i++)
|
||||
{
|
||||
var angle = Angle.FromDegrees(120f * i / (float)segCount - 15f);
|
||||
var thisOffs = angle.RotateVec(up * innerRatio);
|
||||
var thisPointUp = center + thisOffs;
|
||||
var thisPointDown = center - thisOffs;
|
||||
var oldPointUp = center + prevOffs;
|
||||
var oldPointDown = center - prevOffs;
|
||||
handle.DrawLine(oldPointUp, thisPointUp, iconColor);
|
||||
handle.DrawLine(oldPointDown, thisPointDown, iconColor);
|
||||
prevOffs = thisOffs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RadarModeButtonIcon.Anchor:
|
||||
{
|
||||
var iconColor = Color.Blue.WithAlpha(0.95f);
|
||||
var stemTop = center + up * 0.5f;
|
||||
var crossLeft = center + leftOffs * 0.3f + up * 0.7f;
|
||||
var crossRight = center + rightOffs * 0.3f + up * 0.7f;;
|
||||
var leftFluke = center + leftOffs * 0.6f + down * 0.6f;
|
||||
var rightFluke = center + rightOffs * 0.6f + down * 0.6f;
|
||||
var innerRadius = radius * 0.4f;
|
||||
var innerTop = center + up * 0.6f;
|
||||
|
||||
handle.DrawCircle(innerTop, innerRadius, iconColor, false);
|
||||
handle.DrawLine(stemTop, bottom, iconColor);
|
||||
handle.DrawLine(crossLeft, crossRight, iconColor);
|
||||
handle.DrawLine(bottom, leftFluke, iconColor);
|
||||
handle.DrawLine(bottom, rightFluke, iconColor);
|
||||
break;
|
||||
}
|
||||
case RadarModeButtonIcon.Reset:
|
||||
{
|
||||
var iconColor = Color.Red.WithAlpha(0.95f);
|
||||
handle.DrawLine(left, right, iconColor);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mono - evil hack
|
||||
@@ -153,18 +437,15 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
if (!_draggin || _coordinates == null)
|
||||
return;
|
||||
|
||||
if (!_wasPanned)
|
||||
{
|
||||
_wasPanned = true;
|
||||
_oldCoordinates = RelativePanning ? _coordinates.Value : _transform.ToCoordinates(_transform.ToMapCoordinates(_coordinates.Value));
|
||||
if (!RelativePanning)
|
||||
_rotation = new Angle(0);
|
||||
}
|
||||
if (_oldCoordinates == null)
|
||||
return;
|
||||
if (!_relativePanning
|
||||
&& _coordinates?.EntityId is { } coordEnt
|
||||
&& EntManager.TryGetComponent<TransformComponent>(coordEnt, out var coordXform)
|
||||
&& coordXform.MapUid != coordEnt
|
||||
)
|
||||
_coordinates = _transform.ToCoordinates(_transform.ToMapCoordinates(_coordinates.Value));
|
||||
|
||||
var offs = (_rotation ?? new Angle(0)).RotateVec(InverseMapPosition(MidPointVector));
|
||||
_coordinates = _oldCoordinates.Value.Offset(offs);
|
||||
|
||||
_wasPanned = true;
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
@@ -184,21 +465,11 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isMouseDown = false;
|
||||
|
||||
if (_coordinates == null || _rotation == null || OnRadarClick == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var a = InverseScalePosition(args.RelativePosition);
|
||||
var relativeWorldPos = a with { Y = -a.Y };
|
||||
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
|
||||
var coords = _coordinates.Value.Offset(relativeWorldPos);
|
||||
var coords = GetMouseEntityCoordinates(args.RelativePosition);
|
||||
OnRadarClick?.Invoke(coords);
|
||||
}
|
||||
|
||||
@@ -235,13 +506,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
|
||||
private void TryFireAtPosition(Vector2 relativePosition)
|
||||
{
|
||||
if (_coordinates == null || _rotation == null || OnRadarClick == null)
|
||||
return;
|
||||
|
||||
var a = InverseScalePosition(relativePosition);
|
||||
var relativeWorldPos = new Vector2(a.X, -a.Y);
|
||||
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
|
||||
var coords = _coordinates.Value.Offset(relativeWorldPos);
|
||||
var coords = GetMouseEntityCoordinates(relativePosition);
|
||||
OnRadarClick?.Invoke(coords);
|
||||
}
|
||||
|
||||
@@ -257,14 +522,13 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
}
|
||||
|
||||
var pos = _uiManager.MousePositionScaled.Position - GlobalPosition;
|
||||
var relativeWorldPos = _rotation.Value.RotateVec(pos);
|
||||
|
||||
// I am not sure why the resulting point is 20 units under the mouse.
|
||||
return _coordinates.Value.Offset(relativeWorldPos);
|
||||
return GetMouseEntityCoordinates(pos);
|
||||
}
|
||||
|
||||
public void UpdateState(NavInterfaceState state)
|
||||
{
|
||||
EnsureRadarModeButtons();
|
||||
ApplyRadarModeDefaults();
|
||||
SetMatrix(EntManager.GetCoordinates(state.Coordinates), state.Angle);
|
||||
|
||||
WorldMaxRange = state.MaxRange;
|
||||
@@ -279,14 +543,6 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
|
||||
ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange);
|
||||
|
||||
// Mono
|
||||
RelativePanning = state.RelativePanning;
|
||||
Draggable = state.Pannable;
|
||||
if (!Draggable)
|
||||
_wasPanned = false; // also reset
|
||||
|
||||
RotateWithEntity = state.RotateWithEntity;
|
||||
|
||||
// Frontier
|
||||
if (state.MaxIffRange != null)
|
||||
MaximumIFFDistance = state.MaxIffRange.Value;
|
||||
@@ -321,12 +577,24 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
return;
|
||||
}
|
||||
|
||||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
|
||||
var posMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, _rotation.Value);
|
||||
var ourEntRot = _wasPanned && !RelativePanning ? new Angle(0) : RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
|
||||
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||
var shuttleToWorld = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
|
||||
Matrix3x2.Invert(shuttleToWorld, out var worldToShuttle);
|
||||
// Mono
|
||||
if (EntManager.TryGetComponent<RadarConsoleComponent>(_consoleEntity, out var radar))
|
||||
{
|
||||
Draggable = radar.Pannable;
|
||||
}
|
||||
// Mono - TODO: remove _rotation and make it a method instead
|
||||
var coordEnt = _coordinates.Value.EntityId;
|
||||
var coordEntRot = _transform.GetWorldRotation(coordEnt);
|
||||
if (_angleFollow && EntManager.TryGetComponent<TransformComponent>(coordEnt, out var coordXform))
|
||||
_rotation = coordEntRot;
|
||||
else
|
||||
_rotation = Angle.Zero;
|
||||
|
||||
var worldRot = _rotation.Value;
|
||||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value).Offset(_rotation.Value.RotateVec(Offset));
|
||||
var mapCoord = _transform.ToCoordinates(mapPos);
|
||||
var worldToShuttle = Matrix3Helpers.CreateTranslation(-mapCoord.Position) * Matrix3Helpers.CreateRotation(-worldRot);
|
||||
Matrix3x2.Invert(worldToShuttle, out var shuttleToWorld);
|
||||
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
||||
var worldToView = worldToShuttle * shuttleToView;
|
||||
|
||||
@@ -334,8 +602,23 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
DrawShields(handle, xform, worldToShuttle);
|
||||
|
||||
// Frontier Corvax: north line drawing
|
||||
var rot = ourEntRot + _rotation.Value;
|
||||
DrawNorthLine(handle, rot);
|
||||
DrawNorthLine(handle, worldRot);
|
||||
|
||||
// Mono
|
||||
switch (_azimuthMode)
|
||||
{
|
||||
case RadarAzimuthMode.Edge:
|
||||
DrawAzimuthScale(handle, worldRot);
|
||||
break;
|
||||
case RadarAzimuthMode.Ring:
|
||||
DrawAzimuthRing(handle, worldRot);
|
||||
break;
|
||||
}
|
||||
|
||||
if (worldRot != Angle.Zero)
|
||||
{
|
||||
DrawCompassOverlay(handle, coordEntRot);
|
||||
}
|
||||
|
||||
// Draw our grid in detail
|
||||
var ourGridId = xform.GridUid;
|
||||
@@ -361,7 +644,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, radarPosVerts, Color.Lime);
|
||||
|
||||
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), rot, mapPos.Position);
|
||||
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), worldRot, mapPos.Position);
|
||||
var viewAABB = viewBounds.CalcBoundingBox();
|
||||
|
||||
_grids.Clear();
|
||||
@@ -426,9 +709,8 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
//shouldDrawIFF = NfCheckShouldDrawIffRangeCondition(shouldDrawIFF, mapCenter, curGridToWorld); // Frontier code
|
||||
// Frontier: range checks // Mono
|
||||
var gridMapPos = _transform.ToMapCoordinates(new EntityCoordinates(gUid, gridBody.LocalCenter)).Position;
|
||||
var ourPos = _transform.ToMapCoordinates(_coordinates.Value);
|
||||
if (!hideLabel) // Mono - show thermal signatures even at long range
|
||||
shouldDrawIFF = NfCheckShouldDrawIffRangeCondition(shouldDrawIFF, ourPos.Position - gridMapPos);
|
||||
shouldDrawIFF = NfCheckShouldDrawIffRangeCondition(shouldDrawIFF, mapPos.Position - gridMapPos);
|
||||
// End Frontier
|
||||
|
||||
// Mono
|
||||
@@ -629,7 +911,7 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
if (blip.Config.RespectZoom)
|
||||
box.Box = new Box2(box.Box.BottomLeft * MinimapScale, box.Box.TopRight * MinimapScale);
|
||||
if (blip.Config.Rotate)
|
||||
box.Rotation = ourEntRot - blip.Rotation;
|
||||
box.Rotation = worldRot - blip.Rotation;
|
||||
|
||||
if (blip.GridUid is { } grid)
|
||||
{
|
||||
@@ -873,11 +1155,24 @@ public partial class ShuttleNavControl : BaseShuttleControl // Mono
|
||||
|
||||
protected Vector2 InverseScalePosition(Vector2 value)
|
||||
{
|
||||
// Account for UI scaling: value is unscaled, so adjust by UIScale
|
||||
var scaledValue = value * UIScale;
|
||||
return (scaledValue - MidPointVector) / MinimapScale;
|
||||
}
|
||||
|
||||
// Mono
|
||||
protected EntityCoordinates GetMouseEntityCoordinates(Vector2 relativePosition)
|
||||
{
|
||||
if (_coordinates is not { } cord || _rotation is not { } rot)
|
||||
return new();
|
||||
|
||||
// multiply by UIScale because UI is Very Intelligent and doesn't scale mouse position for whatever reason
|
||||
var screenRelativeWorldPos = InverseMapPosition(relativePosition * UIScale);
|
||||
var relativeWorldPos = rot.RotateVec(screenRelativeWorldPos);
|
||||
var coordEntRot = _transform.GetWorldRotation(cord.EntityId);
|
||||
var coords = cord.Offset((-coordEntRot).RotateVec(relativeWorldPos));
|
||||
return coords;
|
||||
}
|
||||
|
||||
public class BlipData
|
||||
{
|
||||
public bool IsOutsideRadarCircle { get; set; }
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
namespace Content.Client.Toggleable;
|
||||
|
||||
/// <summary>
|
||||
/// Component that handles the toggling the visuals of some light emitting entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will toggle the visibility of layers on an entity's sprite, the in-hand visuals, and the clothing/equipment
|
||||
/// visuals. This will modify the color of any attached point lights.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ToggleableLightVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Sprite layer that will have its visibility toggled when this item is toggled.
|
||||
/// </summary>
|
||||
[DataField("spriteLayer")]
|
||||
public string? SpriteLayer = "light";
|
||||
|
||||
/// <summary>
|
||||
/// Layers to add to the sprite of the player that is holding this entity (while the component is toggled on).
|
||||
/// </summary>
|
||||
[DataField("inhandVisuals")]
|
||||
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
|
||||
|
||||
/// <summary>
|
||||
/// Layers to add to the sprite of the player that is wearing this entity (while the component is toggled on).
|
||||
/// </summary>
|
||||
[DataField("clothingVisuals")]
|
||||
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
using Content.Client.Clothing;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Toggleable;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Toggleable;
|
||||
|
||||
public sealed class ToggleableLightVisualsSystem : VisualizerSystem<ToggleableLightVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSys = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _lights = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ToggleableLightVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals, after: new[] { typeof(ItemSystem) });
|
||||
SubscribeLocalEvent<ToggleableLightVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals, after: new[] { typeof(ClientClothingSystem) });
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, ToggleableLightVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, args.Component))
|
||||
return;
|
||||
|
||||
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, args.Component);
|
||||
|
||||
// Update the item's sprite
|
||||
if (args.Sprite != null && component.SpriteLayer != null && args.Sprite.LayerMapTryGet(component.SpriteLayer, out var layer))
|
||||
{
|
||||
args.Sprite.LayerSetVisible(layer, enabled);
|
||||
if (modulate)
|
||||
args.Sprite.LayerSetColor(layer, color);
|
||||
}
|
||||
|
||||
// Update any point-lights
|
||||
if (TryComp(uid, out PointLightComponent? light))
|
||||
{
|
||||
DebugTools.Assert(!light.NetSyncEnabled, "light visualizers require point lights without net-sync");
|
||||
_lights.SetEnabled(uid, enabled, light);
|
||||
if (enabled && modulate)
|
||||
{
|
||||
_lights.SetColor(uid, color, light);
|
||||
}
|
||||
}
|
||||
|
||||
// update clothing & in-hand visuals.
|
||||
_itemSys.VisualsChanged(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the unshaded light overlays to any clothing sprites.
|
||||
/// </summary>
|
||||
private void OnGetEquipmentVisuals(EntityUid uid, ToggleableLightVisualsComponent component, GetEquipmentVisualsEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance)
|
||||
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, appearance)
|
||||
|| !enabled)
|
||||
return;
|
||||
|
||||
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
|
||||
return;
|
||||
List<PrototypeLayerData>? layers = null;
|
||||
|
||||
// attempt to get species specific data
|
||||
if (inventory.SpeciesId != null)
|
||||
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
|
||||
|
||||
// No species specific data. Try to default to generic data.
|
||||
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
|
||||
return;
|
||||
|
||||
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
|
||||
|
||||
var i = 0;
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
var key = layer.MapKeys?.FirstOrDefault();
|
||||
if (key == null)
|
||||
{
|
||||
key = i == 0 ? $"{args.Slot}-toggle" : $"{args.Slot}-toggle-{i}";
|
||||
i++;
|
||||
}
|
||||
|
||||
if (modulate)
|
||||
layer.Color = color;
|
||||
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetHeldVisuals(EntityUid uid, ToggleableLightVisualsComponent component, GetInhandVisualsEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance)
|
||||
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, appearance)
|
||||
|| !enabled)
|
||||
return;
|
||||
|
||||
if (!component.InhandVisuals.TryGetValue(args.Location, out var layers))
|
||||
return;
|
||||
|
||||
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
|
||||
|
||||
var i = 0;
|
||||
var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}-toggle";
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
var key = layer.MapKeys?.FirstOrDefault();
|
||||
if (key == null)
|
||||
{
|
||||
key = i == 0 ? defaultKey : $"{defaultKey}-{i}";
|
||||
i++;
|
||||
}
|
||||
|
||||
if (modulate)
|
||||
layer.Color = color;
|
||||
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
namespace Content.Client.Toggleable;
|
||||
|
||||
/// <summary>
|
||||
/// Component that handles toggling the visuals of an entity, including layers on an entity's sprite,
|
||||
/// the in-hand visuals, and the clothing/equipment visuals.
|
||||
/// </summary>
|
||||
/// <see cref="ToggleableVisualsSystem"/>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ToggleableVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Sprite layer that will have its visibility toggled when this item is toggled.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string? SpriteLayer;
|
||||
|
||||
/// <summary>
|
||||
/// Layers to add to the sprite of the player that is holding this entity (while the component is toggled on).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
|
||||
|
||||
/// <summary>
|
||||
/// Layers to add to the sprite of the player that is wearing this entity (while the component is toggled on).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Clothing;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Toggleable;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Toggleable;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the behavior of <see cref="ToggleableVisualsComponent"/> by reacting to
|
||||
/// <see cref="AppearanceChangeEvent"/>, for the sprite directly; <see cref="OnGetHeldVisuals"/> for the
|
||||
/// in-hand visuals; and <see cref="OnGetEquipmentVisuals"/> for the clothing visuals.
|
||||
/// </summary>
|
||||
/// <see cref="ToggleableVisualsComponent"/>
|
||||
public sealed class ToggleableVisualsSystem : VisualizerSystem<ToggleableVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ToggleableVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals,
|
||||
after: [typeof(ItemSystem)]);
|
||||
SubscribeLocalEvent<ToggleableVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals,
|
||||
after: [typeof(ClientClothingSystem)]);
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid,
|
||||
ToggleableVisualsComponent component,
|
||||
ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, args.Component))
|
||||
return;
|
||||
|
||||
var modulateColor =
|
||||
AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, args.Component);
|
||||
|
||||
// Update the item's sprite
|
||||
if (args.Sprite != null && component.SpriteLayer != null &&
|
||||
SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.SpriteLayer, out var layer, false))
|
||||
{
|
||||
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, enabled);
|
||||
if (modulateColor)
|
||||
SpriteSystem.LayerSetColor((uid, args.Sprite), component.SpriteLayer, color);
|
||||
}
|
||||
|
||||
// If there's a `ItemTogglePointLightComponent` that says to apply the color to attached lights, do so.
|
||||
if (TryComp<ItemTogglePointLightComponent>(uid, out var toggleLights) &&
|
||||
TryComp(uid, out PointLightComponent? light))
|
||||
{
|
||||
DebugTools.Assert(!light.NetSyncEnabled,
|
||||
$"{typeof(ItemTogglePointLightComponent)} requires point lights without net-sync");
|
||||
_pointLight.SetEnabled(uid, enabled, light);
|
||||
if (modulateColor && toggleLights.ToggleableVisualsColorModulatesLights)
|
||||
{
|
||||
_pointLight.SetColor(uid, color, light);
|
||||
}
|
||||
}
|
||||
|
||||
// update clothing & in-hand visuals.
|
||||
_item.VisualsChanged(uid);
|
||||
}
|
||||
|
||||
private void OnGetEquipmentVisuals(EntityUid uid,
|
||||
ToggleableVisualsComponent component,
|
||||
GetEquipmentVisualsEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance)
|
||||
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, appearance)
|
||||
|| !enabled)
|
||||
return;
|
||||
|
||||
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
|
||||
return;
|
||||
List<PrototypeLayerData>? layers = null;
|
||||
|
||||
// attempt to get species specific data
|
||||
if (inventory.SpeciesId != null)
|
||||
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
|
||||
|
||||
// No species specific data. Try to default to generic data.
|
||||
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
|
||||
return;
|
||||
|
||||
var modulateColor = AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, appearance);
|
||||
|
||||
var i = 0;
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
var key = layer.MapKeys?.FirstOrDefault();
|
||||
if (key == null)
|
||||
{
|
||||
key = i == 0 ? $"{args.Slot}-toggle" : $"{args.Slot}-toggle-{i}";
|
||||
i++;
|
||||
}
|
||||
|
||||
if (modulateColor)
|
||||
layer.Color = color;
|
||||
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetHeldVisuals(EntityUid uid, ToggleableVisualsComponent component, GetInhandVisualsEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance)
|
||||
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, appearance)
|
||||
|| !enabled)
|
||||
return;
|
||||
|
||||
if (!component.InhandVisuals.TryGetValue(args.Location, out var layers))
|
||||
return;
|
||||
|
||||
var modulateColor = AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, appearance);
|
||||
|
||||
var i = 0;
|
||||
var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}-toggle";
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
var key = layer.MapKeys?.FirstOrDefault();
|
||||
if (key == null)
|
||||
{
|
||||
key = i == 0 ? defaultKey : $"{defaultKey}-{i}";
|
||||
i++;
|
||||
}
|
||||
|
||||
if (modulateColor)
|
||||
layer.Color = color;
|
||||
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
}
|
||||
|
||||
// Make it so it doesn't hightlight your own name when you are talking
|
||||
keyword = StartAtSign.Replace(keyword, @"(?<=\[BubbleContent\].*)");
|
||||
keyword = StartAtSign.Replace(keyword, @"(?<=\[BubbleContent\].*|""\[/color\].*)");
|
||||
|
||||
return keyword;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed class DamageOverlayUiController : UIController
|
||||
{
|
||||
if (mobState == null && !EntityManager.TryGetComponent(entity, out mobState) ||
|
||||
thresholds == null && !EntityManager.TryGetComponent(entity, out thresholds) ||
|
||||
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))
|
||||
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))
|
||||
return;
|
||||
|
||||
if (!_mobThresholdSystem.TryGetIncapThreshold(entity, out var foundThreshold, thresholds))
|
||||
@@ -94,47 +94,48 @@ public sealed class DamageOverlayUiController : UIController
|
||||
switch (mobState.CurrentState)
|
||||
{
|
||||
case MobState.Alive:
|
||||
{
|
||||
if (EntityManager.HasComponent<PainNumbnessComponent>(entity))
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
else if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
|
||||
{
|
||||
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
|
||||
}
|
||||
if (!EntityManager.HasComponent<PainNumbnessComponent>(entity)) // Mono - makes this look better
|
||||
{
|
||||
if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
|
||||
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
|
||||
if (damageable.DamagePerGroup.TryGetValue("Burn", out var burnDamage)) // Adds burn damage to pain overlay - Mono
|
||||
_overlay.BruteLevel = FixedPoint2.Min(1f, _overlay.BruteLevel + (burnDamage / critThreshold)).Float();
|
||||
}
|
||||
else
|
||||
_overlay.BruteLevel = 0;
|
||||
|
||||
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
|
||||
{
|
||||
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
|
||||
}
|
||||
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
|
||||
{
|
||||
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
|
||||
}
|
||||
|
||||
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
|
||||
_overlay.CritLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
}
|
||||
_overlay.CritLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
}
|
||||
case MobState.Critical:
|
||||
{
|
||||
if (!_mobThresholdSystem.TryGetDeadPercentage(entity,
|
||||
FixedPoint2.Max(0.0, damageable.TotalDamage), out var critLevel))
|
||||
return;
|
||||
_overlay.CritLevel = critLevel.Value.Float();
|
||||
{
|
||||
if (!_mobThresholdSystem.TryGetDeadPercentage(entity,
|
||||
FixedPoint2.Max(0.0, damageable.TotalDamage), out var critLevel))
|
||||
return;
|
||||
_overlay.CritLevel = critLevel.Value.Float();
|
||||
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
}
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
}
|
||||
case MobState.Dead:
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.CritLevel = 0;
|
||||
break;
|
||||
}
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.CritLevel = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Content.Client.VendingMachines.UI
|
||||
/// Populates the list of available items on the vending machine interface
|
||||
/// and sets icons based on their prototypes
|
||||
/// </summary>
|
||||
public void Populate(List<VendingMachineInventoryEntry> inventory, float priceModifier, int balance, int? cashSlotBalance) // Frontier: add balance, cashSlotBalance
|
||||
public void Populate(List<VendingMachineInventoryEntry> inventory, float priceModifier, int balance, int? cashSlotBalance, bool requiresCash) // Frontier: add balance, cashSlotBalance
|
||||
{
|
||||
UpdateBalance(balance); // Frontier
|
||||
UpdateCashSlotBalance(cashSlotBalance); // Frontier
|
||||
@@ -124,84 +124,11 @@ namespace Content.Client.VendingMachines.UI
|
||||
_dummies.Add(entry.ID, dummy);
|
||||
}
|
||||
|
||||
// Frontier: item pricing
|
||||
// ok so we dont really have access to the pricing system so we are doing a quick price check
|
||||
// based on prototype info since the items inside a vending machine dont actually exist as entities
|
||||
// until they are spawned. So this little alg does the following:
|
||||
// first, checks for a staticprice component, and if it has one, checks to make sure its not 0 since
|
||||
// stacks and other items have 0 cost.
|
||||
// If the price is 0, then we check for both a stack price and a stack component, since if it has one
|
||||
// it should have the other too, and then calculates the price based on that.
|
||||
// If the price is still 0 or non-existant (this is the case for food and containers since their value is
|
||||
// determined dynamically by their contents/inventory), it then falls back to the default mystery
|
||||
// hardcoded value of 20xMarketModifier.
|
||||
var cost = 20;
|
||||
if (prototype != null && prototype.TryGetComponent<StaticPriceComponent>(out var priceComponent, _entityManager.ComponentFactory))
|
||||
{
|
||||
if (priceComponent.Price != 0)
|
||||
{
|
||||
var price = (float)priceComponent.Price;
|
||||
cost = (int)(price * priceModifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prototype.TryGetComponent<StackPriceComponent>(out var stackPrice, _entityManager.ComponentFactory)
|
||||
&& prototype.TryGetComponent<StackComponent>(out var stack, _entityManager.ComponentFactory))
|
||||
{
|
||||
var price = stackPrice.Price * stack.Count;
|
||||
cost = (int)(price * priceModifier);
|
||||
}
|
||||
else
|
||||
cost = (int)(cost * priceModifier);
|
||||
}
|
||||
}
|
||||
else
|
||||
cost = (int)(cost * priceModifier);
|
||||
|
||||
if (prototype != null && prototype.TryGetComponent<SolutionContainerManagerComponent>(out var priceSolutions, _entityManager.ComponentFactory))
|
||||
{
|
||||
if (priceSolutions.Solutions != null)
|
||||
{
|
||||
foreach (var solution in priceSolutions.Solutions.Values)
|
||||
{
|
||||
foreach (var (reagent, quantity) in solution.Contents)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype,
|
||||
out var reagentProto))
|
||||
continue;
|
||||
|
||||
// TODO check ReagentData for price information?
|
||||
var costReagent = quantity.Float() * reagentProto.PricePerUnit;
|
||||
cost += (int)(costReagent * priceModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// End Frontier: item pricing
|
||||
|
||||
// Frontier: calculate vending price (this duplicates Content.Server.PricingSystem.GetVendPrice - this should be moved to Content.Shared if possible)
|
||||
if (prototype != null)
|
||||
{
|
||||
var price = 0.0;
|
||||
|
||||
if (prototype.TryGetComponent<StaticPriceComponent>(out var staticComp, _entityManager.ComponentFactory) && staticComp.VendPrice > 0.0)
|
||||
{
|
||||
price += staticComp.VendPrice;
|
||||
}
|
||||
else if (prototype.TryGetComponent<StackPriceComponent>(out var stackComp, _entityManager.ComponentFactory) && stackComp.VendPrice > 0.0)
|
||||
{
|
||||
price += stackComp.VendPrice;
|
||||
}
|
||||
|
||||
// If there is anything that explicitly sets vending price - higher OR lower, override the base.
|
||||
if (price > 0.0)
|
||||
{
|
||||
cost = (int)price;
|
||||
}
|
||||
}
|
||||
// End Frontier
|
||||
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
var cost = 0; // mono
|
||||
if (requiresCash) // frontier
|
||||
cost = GetPrice(entry, prototype, priceModifier);
|
||||
|
||||
string itemText;
|
||||
|
||||
// Frontier: unlimited vending
|
||||
@@ -211,7 +138,6 @@ namespace Content.Client.VendingMachines.UI
|
||||
itemText = $"[{BankSystemExtensions.ToSpesoString(cost)}] {itemName}";
|
||||
// End Frontier: unlimited vending
|
||||
|
||||
|
||||
if (itemText.Length > longestEntry.Length)
|
||||
longestEntry = itemText;
|
||||
|
||||
@@ -223,6 +149,88 @@ namespace Content.Client.VendingMachines.UI
|
||||
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
|
||||
}
|
||||
|
||||
// Mono: Moved out frontier pricing logic to the separate method
|
||||
private int GetPrice(VendingMachineInventoryEntry entry, EntityPrototype? prototype, float priceModifier){
|
||||
// Frontier: item pricing
|
||||
// ok so we dont really have access to the pricing system so we are doing a quick price check
|
||||
// based on prototype info since the items inside a vending machine dont actually exist as entities
|
||||
// until they are spawned. So this little alg does the following:
|
||||
// first, checks for a staticprice component, and if it has one, checks to make sure its not 0 since
|
||||
// stacks and other items have 0 cost.
|
||||
// If the price is 0, then we check for both a stack price and a stack component, since if it has one
|
||||
// it should have the other too, and then calculates the price based on that.
|
||||
// If the price is still 0 or non-existant (this is the case for food and containers since their value is
|
||||
// determined dynamically by their contents/inventory), it then falls back to the default mystery
|
||||
// hardcoded value of 20xMarketModifier.
|
||||
var cost = 20;
|
||||
if (prototype != null && prototype.TryGetComponent<StaticPriceComponent>(out var priceComponent, _entityManager.ComponentFactory))
|
||||
{
|
||||
if (priceComponent.Price != 0)
|
||||
{
|
||||
var price = (float)priceComponent.Price;
|
||||
cost = (int)(price * priceModifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prototype.TryGetComponent<StackPriceComponent>(out var stackPrice, _entityManager.ComponentFactory)
|
||||
&& prototype.TryGetComponent<StackComponent>(out var stack, _entityManager.ComponentFactory))
|
||||
{
|
||||
var price = stackPrice.Price * stack.Count;
|
||||
cost = (int)(price * priceModifier);
|
||||
}
|
||||
else
|
||||
cost = (int)(cost * priceModifier);
|
||||
}
|
||||
}
|
||||
else
|
||||
cost = (int)(cost * priceModifier);
|
||||
|
||||
if (prototype != null && prototype.TryGetComponent<SolutionContainerManagerComponent>(out var priceSolutions, _entityManager.ComponentFactory))
|
||||
{
|
||||
if (priceSolutions.Solutions != null)
|
||||
{
|
||||
foreach (var solution in priceSolutions.Solutions.Values)
|
||||
{
|
||||
foreach (var (reagent, quantity) in solution.Contents)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype,
|
||||
out var reagentProto))
|
||||
continue;
|
||||
|
||||
// TODO check ReagentData for price information?
|
||||
var costReagent = quantity.Float() * reagentProto.PricePerUnit;
|
||||
cost += (int)(costReagent * priceModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// End Frontier: item pricing
|
||||
|
||||
// Frontier: calculate vending price (this duplicates Content.Server.PricingSystem.GetVendPrice - this should be moved to Content.Shared if possible)
|
||||
if (prototype != null)
|
||||
{
|
||||
var price = 0.0;
|
||||
|
||||
if (prototype.TryGetComponent<StaticPriceComponent>(out var staticComp, _entityManager.ComponentFactory) && staticComp.VendPrice > 0.0)
|
||||
{
|
||||
price += staticComp.VendPrice;
|
||||
}
|
||||
else if (prototype.TryGetComponent<StackPriceComponent>(out var stackComp, _entityManager.ComponentFactory) && stackComp.VendPrice > 0.0)
|
||||
{
|
||||
price += stackComp.VendPrice;
|
||||
}
|
||||
|
||||
// If there is anything that explicitly sets vending price - higher OR lower, override the base.
|
||||
if (price > 0.0)
|
||||
{
|
||||
cost = (int)price;
|
||||
}
|
||||
}
|
||||
|
||||
return cost;
|
||||
// End Frontier
|
||||
}
|
||||
|
||||
// Frontier
|
||||
public void UpdateBalance(int balance)
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Content.Client.VendingMachines
|
||||
[ViewVariables]
|
||||
private int _cashSlotBalance = 0;
|
||||
// End Frontier
|
||||
[ViewVariables]
|
||||
private bool _requiresCash; // mono
|
||||
|
||||
public VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
@@ -74,6 +76,7 @@ namespace Content.Client.VendingMachines
|
||||
if (EntMan.TryGetComponent<VendingMachineComponent>(Owner, out var vendingMachine))
|
||||
{
|
||||
_cashSlotBalance = vendingMachine.CashSlotBalance;
|
||||
_requiresCash = vendingMachine.RequiresCash; // mono
|
||||
if (vendingMachine.CashSlotName != null)
|
||||
cashSlotValue = _cashSlotBalance;
|
||||
}
|
||||
@@ -83,7 +86,7 @@ namespace Content.Client.VendingMachines
|
||||
}
|
||||
// End Frontier
|
||||
|
||||
_menu?.Populate(_cachedInventory, _mod, _balance, cashSlotValue); // Frontier: add _balance
|
||||
_menu?.Populate(_cachedInventory, _mod, _balance, cashSlotValue, _requiresCash); // Frontier: add _balance, mono: add _requiresCash
|
||||
}
|
||||
|
||||
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs;
|
||||
|
||||
namespace Content.Client._CorvaxNext.Silicons.Borgs;
|
||||
|
||||
public sealed partial class AiRemoteControlSystem : SharedAiRemoteControlSystem
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Margin="0 0 0 10">
|
||||
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundDark">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="5 5 5 5">
|
||||
<customControls:HSeparator Margin="0 5 0 5"/>
|
||||
<RichTextLabel Name="DeviceName"/>
|
||||
<BoxContainer Name="RemoteControlButtons" Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
|
||||
<Button Name="MoveButton" StyleClasses="chatSelectorOptionButton" Text="{Loc ai-remote-ui-menu-moveto}"/>
|
||||
<Control Margin="2 0 0 0"/>
|
||||
<Button Name="TakeControlButton" StyleClasses="chatSelectorOptionButton" Text="{Loc ai-remote-control}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
|
||||
|
||||
namespace Content.Client._CorvaxNext.Silicons.Laws.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RemoteDeviceDisplay : Control
|
||||
{
|
||||
public event Action<RemoteDeviceActionEvent>? OnRemoteDeviceAction;
|
||||
|
||||
public RemoteDeviceDisplay(NetEntity netEntityUid, string displayName)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
DeviceName.SetMessage(displayName);
|
||||
|
||||
MoveButton.OnPressed += _ =>
|
||||
{
|
||||
OnRemoteDeviceAction?.Invoke(new RemoteDeviceActionEvent(
|
||||
RemoteDeviceActionEvent.RemoteDeviceActionType.MoveToDevice,
|
||||
netEntityUid));
|
||||
};
|
||||
|
||||
TakeControlButton.OnPressed += _ =>
|
||||
{
|
||||
OnRemoteDeviceAction?.Invoke(new RemoteDeviceActionEvent(
|
||||
RemoteDeviceActionEvent.RemoteDeviceActionType.TakeControl,
|
||||
netEntityUid));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared._CorvaxNext.Silicons.Borgs.Components.AiRemoteControllerComponent;
|
||||
|
||||
namespace Content.Client._CorvaxNext.Silicons.Laws.Ui;
|
||||
|
||||
public sealed class RemoteDevicesBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private RemoteDevicesMenu? _menu;
|
||||
private EntityUid _owner;
|
||||
|
||||
public RemoteDevicesBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<RemoteDevicesMenu>();
|
||||
_menu.OnRemoteDeviceAction += SendAction;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not RemoteDevicesBuiState msg)
|
||||
return;
|
||||
|
||||
_menu?.Update(_owner, msg);
|
||||
}
|
||||
|
||||
public void SendAction(RemoteDeviceActionEvent action)
|
||||
{
|
||||
SendMessage(new RemoteDeviceActionMessage(action));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc ai-remote-ui-menu-title}"
|
||||
MinSize="355 100"
|
||||
SetSize="355 415">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<PanelContainer VerticalExpand="True" Margin="10 10 10 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer
|
||||
HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Name="RemoteDevicesDisplayContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
Margin="10 10 10 0">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
|
||||
|
||||
namespace Content.Client._CorvaxNext.Silicons.Laws.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RemoteDevicesMenu : FancyWindow
|
||||
{
|
||||
public event Action<RemoteDeviceActionEvent>? OnRemoteDeviceAction;
|
||||
|
||||
public RemoteDevicesMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void Update(EntityUid uid, RemoteDevicesBuiState state)
|
||||
{
|
||||
RemoteDevicesDisplayContainer.Children.Clear();
|
||||
|
||||
if (state.DeviceList == null)
|
||||
return;
|
||||
|
||||
foreach (var device in state.DeviceList)
|
||||
{
|
||||
var control = new RemoteDeviceDisplay(device.NetEntityUid, device.DisplayName);
|
||||
|
||||
control.OnRemoteDeviceAction += action =>
|
||||
{
|
||||
OnRemoteDeviceAction?.Invoke(action);
|
||||
};
|
||||
|
||||
RemoteDevicesDisplayContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,13 @@
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<ui:ShuttleNavControl Name="NavRadar"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
MinHeight="400"/>
|
||||
<!-- Mono - wrap -->
|
||||
<LayoutContainer>
|
||||
<ui:ShuttleNavControl Name="NavRadar"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
MinHeight="400"/>
|
||||
</LayoutContainer>
|
||||
|
||||
</GridContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls;assembly=Content.Client"
|
||||
BackButtonStyleClass="RadialMenuBackButton"
|
||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="450 450">
|
||||
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"
|
||||
ReserveSpaceForHiddenChildren="False" />
|
||||
|
||||
</ui:RadialMenu>
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._Goobstation.Weapons.AmmoSelector;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Goobstation.AmmoSelector;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AmmoSelectorMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly EntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private SpriteSystem _sprites;
|
||||
|
||||
public event Action<ProtoId<SelectableAmmoPrototype>>? SendAmmoSelectorSystemMessageAction;
|
||||
|
||||
private EntityUid _item;
|
||||
|
||||
public AmmoSelectorMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
_sprites = _entManager.System<SpriteSystem>();
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid uid)
|
||||
{
|
||||
_item = uid;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
main.RemoveAllChildren();
|
||||
|
||||
if (!_entManager.TryGetComponent(_item, out AmmoSelectorComponent? ammoSelector))
|
||||
return;
|
||||
|
||||
foreach (var ammo in ammoSelector.Prototypes)
|
||||
{
|
||||
if (!_protoManager.TryIndex(ammo, out var prototype))
|
||||
continue;
|
||||
|
||||
var button = new AmmoSelectorMenuButton
|
||||
{
|
||||
SetSize = new Vector2(64, 64),
|
||||
ToolTip = Loc.GetString(prototype.Desc),
|
||||
ProtoId = prototype.ID
|
||||
};
|
||||
|
||||
var texture = new TextureRect
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Texture = _sprites.Frame0(prototype.Icon),
|
||||
TextureScale = new Vector2(2f, 2f)
|
||||
};
|
||||
|
||||
button.AddChild(texture);
|
||||
main.AddChild(button);
|
||||
}
|
||||
|
||||
AddAmmoSelectorMenuButtonOnClickActions(main);
|
||||
}
|
||||
|
||||
private void AddAmmoSelectorMenuButtonOnClickActions(RadialContainer control)
|
||||
{
|
||||
foreach (var child in control.Children)
|
||||
{
|
||||
if (child is not AmmoSelectorMenuButton castChild)
|
||||
continue;
|
||||
|
||||
castChild.OnButtonUp += _ =>
|
||||
{
|
||||
SendAmmoSelectorSystemMessageAction?.Invoke(castChild.ProtoId);
|
||||
Close();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AmmoSelectorMenuButton : RadialMenuTextureButtonWithSector
|
||||
{
|
||||
public ProtoId<SelectableAmmoPrototype> ProtoId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Shared._Goobstation.Weapons.AmmoSelector;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Goobstation.AmmoSelector;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class AmmoSelectorMenuBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
private AmmoSelectorMenu? _menu;
|
||||
|
||||
public AmmoSelectorMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<AmmoSelectorMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.SendAmmoSelectorSystemMessageAction += SendAmmoSelectorSystemMessage;
|
||||
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
|
||||
}
|
||||
|
||||
public void SendAmmoSelectorSystemMessage(ProtoId<SelectableAmmoPrototype> protoId)
|
||||
{
|
||||
SendPredictedMessage(new AmmoSelectedMessage(protoId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Content.Shared._Mono.CCVar;
|
||||
using Content.Shared._Mono.Company;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Mono.Company;
|
||||
|
||||
public sealed partial class CompanyManager
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private HashSet<ProtoId<CompanyPrototype>> _whitelist = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgCompanyWhitelist>(OnWhitelistMsg);
|
||||
}
|
||||
|
||||
private void OnWhitelistMsg(MsgCompanyWhitelist msg)
|
||||
{
|
||||
_whitelist = msg.Whitelist;
|
||||
}
|
||||
|
||||
public bool IsPlayerWhitelisted(ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
return _whitelist.Contains(company);
|
||||
}
|
||||
|
||||
public bool IsAllowed(ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_config.GetCVar(MonoCVars.CompanyWhitelist))
|
||||
return true;
|
||||
|
||||
if (!_proto.Resolve(company, out var companyPrototype) ||
|
||||
!companyPrototype.Whitelisted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsPlayerWhitelisted(company);
|
||||
}
|
||||
}
|
||||
@@ -68,12 +68,14 @@ public sealed class FireControlNavControl : ShuttleNavControl
|
||||
|
||||
base.Draw(handle);
|
||||
|
||||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
|
||||
var posMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, _rotation.Value);
|
||||
var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
|
||||
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||
var shuttleToWorld = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
|
||||
Matrix3x2.Invert(shuttleToWorld, out var worldToShuttle);
|
||||
var coordEntRot = _transform.GetWorldRotation(_coordinates.Value.EntityId);
|
||||
|
||||
var worldRot = _rotation.Value;
|
||||
|
||||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value).Offset(_rotation.Value.RotateVec(Offset));
|
||||
var mapCoord = _transform.ToCoordinates(mapPos);
|
||||
var worldToShuttle = Matrix3Helpers.CreateTranslation(-mapCoord.Position) * Matrix3Helpers.CreateRotation(-worldRot);
|
||||
Matrix3x2.Invert(worldToShuttle, out var shuttleToWorld);
|
||||
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
||||
var worldToView = worldToShuttle * shuttleToView;
|
||||
Matrix3x2.Invert(worldToView, out var viewToWorld);
|
||||
@@ -128,15 +130,7 @@ public sealed class FireControlNavControl : ShuttleNavControl
|
||||
|
||||
_lastCursorUpdateTime = (float)currentTime;
|
||||
|
||||
// Convert mouse position to world coordinates for missile tracking
|
||||
if (_coordinates == null || _rotation == null || OnRadarClick == null)
|
||||
return;
|
||||
|
||||
var a = InverseScalePosition(relativePosition);
|
||||
var relativeWorldPos = new Vector2(a.X, -a.Y);
|
||||
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
|
||||
var coords = _coordinates.Value.Offset(relativeWorldPos);
|
||||
|
||||
var coords = GetMouseEntityCoordinates(relativePosition);
|
||||
// This will update the server of our cursor position without triggering actual firing
|
||||
OnRadarClick?.Invoke(coords);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,9 @@
|
||||
|
||||
<!-- Radar Display -->
|
||||
<BoxContainer Name="RadarContainer" Orientation="Horizontal" HorizontalExpand="True" Margin="50 50 50 50">
|
||||
<ui:FireControlNavControl Name="NavRadar" />
|
||||
<LayoutContainer>
|
||||
<ui:FireControlNavControl Name="NavRadar" />
|
||||
</LayoutContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -34,11 +34,6 @@ public sealed class PlantAnalyzerBoundUserInterface : BoundUserInterface
|
||||
_window.Populate(cast);
|
||||
}
|
||||
|
||||
public void AdvPressed(bool scanMode)
|
||||
{
|
||||
SendMessage(new PlantAnalyzerSetMode(scanMode));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -3,30 +3,28 @@
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'plant-analyzer-interface-title'}">
|
||||
<!-- Margin="left,top,right,bottom" -->
|
||||
<GridContainer Rows="3" Name ="GridCont" Margin="10 5 10 5" VerticalAlignment="Stretch" HorizontalExpand="True">
|
||||
<BoxContainer Name="Toggle">
|
||||
<Label Name="AdvMode" Text="{Loc 'plant-analyzer-window-scanmode'}" Margin="5 0 5 0"/>
|
||||
<Button Name="OnButton" Text="{Loc 'plant-analyzer-window-mode-on'}" StyleClasses="OpenRight"/>
|
||||
<Button Name="OffButton" Text="{Loc 'plant-analyzer-window-mode-off'}" StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
<GridContainer Rows="2" Name ="GridCont" Margin="10 5 10 5">
|
||||
<BoxContainer Name="Top">
|
||||
<Label Name="NoData" Text="{Loc 'plant-analyzer-window-no-seed-information-text'}" Margin="10 0 0 0"/>
|
||||
</BoxContainer>
|
||||
<TabContainer Name="Tabs" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalExpand="True">
|
||||
<BoxContainer Name ="{Loc 'plant-analyzer-window-tab-basics'}" Orientation="Vertical" VerticalAlignment="Stretch" Margin="10 0 5 0">
|
||||
<Label Name="PlantName" Margin="0 5 0 5"/>
|
||||
<Label Name="PlantYield" Margin="0 5 0 5"/>
|
||||
<Label Name="Potency" Margin="0 5 0 5"/>
|
||||
<Label Name="Repeat" Margin="0 5 0 5"/>
|
||||
<Label Name="Chemicals" Margin="0 5 0 5"/>
|
||||
<Label Name="ConsumeGases" Margin="0 5 0 5" />
|
||||
<Label Name="ExudeGases" Margin="0 5 0 5" />
|
||||
<Label Name="Lifespan" Margin="0 5 0 5"/>
|
||||
<Label Name="Maturation" Margin="0 5 0 5"/>
|
||||
<Label Name="Production" Margin="0 5 0 5"/>
|
||||
<Label Name="GrowthStages" Margin="0 5 0 5"/>
|
||||
<Label Name="Endurance" Margin="0 5 0 5"/>
|
||||
</BoxContainer>
|
||||
<TabContainer Name="Tabs" VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Stretch" MinSize="300 400">
|
||||
<ScrollContainer Name ="{Loc 'plant-analyzer-window-tab-basics'}">
|
||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Stretch" Margin="10 0 5 0">
|
||||
<Label Name="PlantName" Margin="0 5 0 5"/>
|
||||
<Label Name="PlantYield" Margin="0 5 0 5"/>
|
||||
<Label Name="Potency" Margin="0 5 0 5"/>
|
||||
<Label Name="Repeat" Margin="0 5 0 5"/>
|
||||
<Label Name="Lifespan" Margin="0 5 0 5"/>
|
||||
<Label Name="Maturation" Margin="0 5 0 5"/>
|
||||
<Label Name="Production" Margin="0 5 0 5"/>
|
||||
<Label Name="GrowthStages" Margin="0 5 0 5"/>
|
||||
<Label Name="Endurance" Margin="0 5 0 5"/>
|
||||
<Label Name="ConsumeGases" Margin="0 5 0 5" />
|
||||
<Label Name="ExudeGases" Margin="0 5 0 5" />
|
||||
<Label Name="Chemicals" Margin="0 5 0 5"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
|
||||
<BoxContainer Name="{Loc 'plant-analyzer-window-tab-tolerances'}" Orientation="Vertical" VerticalAlignment="Stretch" Margin="10 0 5 0">
|
||||
<Label Name="NutrientUsage" Margin="0 5 0 5"/>
|
||||
<Label Name="WaterUsage" Margin="0 5 0 5"/>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
using Content.Shared._NF.PlantAnalyzer;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
|
||||
@@ -15,8 +10,6 @@ namespace Content.Client._NF.PlantAnalyzer.UI;
|
||||
public sealed partial class PlantAnalyzerWindow : FancyWindow
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
|
||||
private const string IndentedNewline = "\n ";
|
||||
|
||||
public PlantAnalyzerWindow(PlantAnalyzerBoundUserInterface owner)
|
||||
@@ -26,13 +19,6 @@ public sealed partial class PlantAnalyzerWindow : FancyWindow
|
||||
var dependencies = IoCManager.Instance!;
|
||||
_entityManager = dependencies.Resolve<IEntityManager>();
|
||||
|
||||
OnButton.Group = _buttonGroup;
|
||||
OnButton.ToggleMode = true;
|
||||
OffButton.Group = _buttonGroup;
|
||||
OffButton.ToggleMode = true;
|
||||
|
||||
OnButton.OnPressed += _ => owner.AdvPressed(true);
|
||||
OffButton.OnPressed += _ => owner.AdvPressed(false);
|
||||
}
|
||||
|
||||
public void Populate(PlantAnalyzerScannedSeedPlantInformation msg)
|
||||
@@ -47,15 +33,6 @@ public sealed partial class PlantAnalyzerWindow : FancyWindow
|
||||
}
|
||||
NoData.Visible = false;
|
||||
|
||||
if (msg.AdvancedInfo is not null)
|
||||
{
|
||||
OnButton.Pressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
OffButton.Pressed = true;
|
||||
}
|
||||
|
||||
// Process message fields into strings.
|
||||
StringBuilder chemString = new();
|
||||
if (msg.SeedChem != null)
|
||||
@@ -79,60 +56,65 @@ public sealed partial class PlantAnalyzerWindow : FancyWindow
|
||||
Potency.Text = Loc.GetString("plant-analyzer-plant-potency-text", ("seedPotency", $"{msg.SeedPotency:F0}"));
|
||||
Repeat.Text = Loc.GetString("plant-analyzer-plant-harvest-text", ("plantHarvestType", Loc.GetString($"plant-analyzer-harvest-{msg.HarvestType}").ToString()));
|
||||
Endurance.Text = Loc.GetString("plant-analyzer-plant-endurance-text", ("seedEndurance", $"{msg.Endurance:F0}"));
|
||||
Chemicals.Text = Loc.GetString("plant-analyzer-plant-chemistry-text", ("seedChem", chemString));
|
||||
ExudeGases.Text = Loc.GetString("plant-analyzer-plant-exude-text", ("gases", exudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : exudeGases.ToString()));
|
||||
ConsumeGases.Text = Loc.GetString("plant-analyzer-plant-consume-text", ("gases", consudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : consudeGases.ToString()));
|
||||
Lifespan.Text = Loc.GetString("plant-analyzer-plant-lifespan-text", ("lifespan", $"{msg.Lifespan:F1}"));
|
||||
Maturation.Text = Loc.GetString("plant-analyzer-plant-maturation-text", ("maturation", $"{msg.Maturation:F1}"));
|
||||
Production.Text = Loc.GetString("plant-analyzer-plant-production-text", ("production", $"{msg.Production:F1}"));
|
||||
GrowthStages.Text = Loc.GetString("plant-analyzer-plant-growthstages-text", ("growthStages", $"{msg.GrowthStages:D0}"));
|
||||
ConsumeGases.Text = Loc.GetString("plant-analyzer-plant-consume-text", ("gases", consudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : consudeGases.ToString()));
|
||||
ExudeGases.Text = Loc.GetString("plant-analyzer-plant-exude-text", ("gases", exudeGases.Length == 0 ? Loc.GetString("plant-analyzer-plant-gases-none") : exudeGases.ToString()));
|
||||
Chemicals.Text = Loc.GetString("plant-analyzer-plant-chemistry-text", ("seedChem", chemString));
|
||||
// Tolerances
|
||||
var adv = msg.AdvancedInfo;
|
||||
NutrientUsage.Text = Loc.GetString("plant-analyzer-tolerance-nutrient-usage", ("nutrientUsage", adv is null ? "-" : $"{adv.Value.NutrientConsumption:F2}"));
|
||||
WaterUsage.Text = Loc.GetString("plant-analyzer-tolerance-water-usage", ("waterUsage", adv is null ? "-" : $"{adv.Value.WaterConsumption:F2}"));
|
||||
IdealHeat.Text = Loc.GetString("plant-analyzer-tolerance-ideal-heat", ("idealHeat", adv is null ? "-" : $"{adv.Value.IdealHeat:F0}"));
|
||||
HeatTolerance.Text = Loc.GetString("plant-analyzer-tolerance-heat-tolerance", ("heatTolerance", adv is null ? "-" : $"{adv.Value.HeatTolerance:F1}"));
|
||||
IdealLight.Text = Loc.GetString("plant-analyzer-tolerance-ideal-light", ("idealLight", adv is null ? "-" : $"{adv.Value.IdealLight:F1}"));
|
||||
LightTolerance.Text = Loc.GetString("plant-analyzer-tolerance-light-tolerance", ("lightTolerance", adv is null ? "-" : $"{adv.Value.LightTolerance:F1}"));
|
||||
ToxinsTolerance.Text = Loc.GetString("plant-analyzer-tolerance-toxin-tolerance", ("toxinsTolerance", adv is null ? "-" : $"{adv.Value.ToxinsTolerance:F1}"));
|
||||
LowPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-low-pressure", ("lowPressureTolerance", adv is null ? "-" : $"{adv.Value.LowPressureTolerance:F1}")); ;
|
||||
HighPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-high-pressure", ("highPressureTolerance", adv is null ? "-" : $"{adv.Value.HighPressureTolerance:F1}"));
|
||||
PestTolerance.Text = Loc.GetString("plant-analyzer-tolerance-pest-tolerance", ("pestTolerance", adv is null ? "-" : $"{adv.Value.PestTolerance:F1}"));
|
||||
WeedTolerance.Text = Loc.GetString("plant-analyzer-tolerance-weed-tolerance", ("weedTolerance", adv is null ? "-" : $"{adv.Value.WeedTolerance:F1}"));
|
||||
NutrientUsage.Text = Loc.GetString("plant-analyzer-tolerance-nutrient-usage", ("nutrientUsage", $"{msg.NutrientConsumption:F2}"));
|
||||
WaterUsage.Text = Loc.GetString("plant-analyzer-tolerance-water-usage", ("waterUsage", $"{msg.WaterConsumption:F2}"));
|
||||
IdealHeat.Text = Loc.GetString("plant-analyzer-tolerance-ideal-heat", ("idealHeat", $"{msg.IdealHeat:F0}"));
|
||||
HeatTolerance.Text = Loc.GetString("plant-analyzer-tolerance-heat-tolerance", ("heatTolerance", $"{msg.HeatTolerance:F1}"));
|
||||
IdealLight.Text = Loc.GetString("plant-analyzer-tolerance-ideal-light", ("idealLight", $"{msg.IdealLight:F1}"));
|
||||
LightTolerance.Text = Loc.GetString("plant-analyzer-tolerance-light-tolerance", ("lightTolerance", $"{msg.LightTolerance:F1}"));
|
||||
ToxinsTolerance.Text = Loc.GetString("plant-analyzer-tolerance-toxin-tolerance", ("toxinsTolerance", $"{msg.ToxinsTolerance:F1}"));
|
||||
LowPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-low-pressure", ("lowPressureTolerance", $"{msg.LowPressureTolerance:F1}"));
|
||||
HighPressureTolerance.Text = Loc.GetString("plant-analyzer-tolerance-high-pressure", ("highPressureTolerance", $"{msg.HighPressureTolerance:F1}"));
|
||||
PestTolerance.Text = Loc.GetString("plant-analyzer-tolerance-pest-tolerance", ("pestTolerance", $"{msg.PestTolerance:F1}"));
|
||||
WeedTolerance.Text = Loc.GetString("plant-analyzer-tolerance-weed-tolerance", ("weedTolerance", $"{msg.WeedTolerance:F1}"));
|
||||
|
||||
// Misc
|
||||
|
||||
if (adv != null)
|
||||
{
|
||||
var advInst = adv.Value;
|
||||
StringBuilder mutations = new();
|
||||
if (advInst.Mutations.HasFlag(MutationFlags.TurnIntoKudzu))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-turnintokudzu"));
|
||||
}
|
||||
if (advInst.Mutations.HasFlag(MutationFlags.Seedless))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-seedless"));
|
||||
}
|
||||
if (advInst.Mutations.HasFlag(MutationFlags.Ligneous))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-ligneous"));
|
||||
}
|
||||
if (advInst.Mutations.HasFlag(MutationFlags.CanScream))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-canscream"));
|
||||
}
|
||||
StringBuilder mutations = new();
|
||||
|
||||
Traits.Text = Loc.GetString("plant-analyzer-plant-mutations-text", ("traits", mutations.ToString()));
|
||||
}
|
||||
else
|
||||
if (msg.Mutations.HasFlag(MutationFlags.TurnIntoKudzu))
|
||||
{
|
||||
Traits.Text = Loc.GetString("plant-analyzer-plant-mutations-text", ("traits", "-"));
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-turnintokudzu"));
|
||||
}
|
||||
|
||||
if (msg.Mutations.HasFlag(MutationFlags.Seedless))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-seedless"));
|
||||
}
|
||||
|
||||
if (msg.Mutations.HasFlag(MutationFlags.Ligneous))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-ligneous"));
|
||||
}
|
||||
|
||||
if (msg.Mutations.HasFlag(MutationFlags.CanScream))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-canscream"));
|
||||
}
|
||||
|
||||
if (msg.Mutations.HasFlag(MutationFlags.Unviable))
|
||||
{
|
||||
mutations.Append(IndentedNewline);
|
||||
mutations.Append(Loc.GetString("plant-analyzer-mutation-unviable"));
|
||||
}
|
||||
|
||||
var traitsText = mutations.Length == 0 ? "-" : mutations.ToString();
|
||||
|
||||
Traits.Text = Loc.GetString("plant-analyzer-plant-mutations-text", ("traits", traitsText));
|
||||
|
||||
|
||||
StringBuilder speciation = new();
|
||||
if (msg.Speciation is null)
|
||||
{
|
||||
|
||||
@@ -24,15 +24,14 @@ namespace Content.Client.Shuttles.BUI
|
||||
});
|
||||
}
|
||||
|
||||
private void OnMaxShuttleSpeedChanged(NetEntity? entityUid, float maxSpeed)
|
||||
private void OnMaxShuttleSpeedChanged(float? maxSpeed)
|
||||
{
|
||||
SendMessage(new SetMaxShuttleSpeedRequest
|
||||
{
|
||||
ShuttleEntityUid = entityUid,
|
||||
MaxSpeed = maxSpeed,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void OnNetworkPortButtonPressed(string sourcePort, string targetPort)
|
||||
{
|
||||
SendMessage(new ShuttlePortButtonPressedMessage
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// See AGPLv3.txt for details.
|
||||
using Content.Shared._NF.Shuttles.Events;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
|
||||
namespace Content.Client.Shuttles.UI
|
||||
{
|
||||
@@ -10,7 +12,7 @@ namespace Content.Client.Shuttles.UI
|
||||
{
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
public event Action<NetEntity?, InertiaDampeningMode>? OnInertiaDampeningModeChanged;
|
||||
public event Action<NetEntity?, float>? OnMaxShuttleSpeedChanged;
|
||||
public event Action<float?>? OnMaxShuttleSpeedChanged;
|
||||
public event Action<string, string>? OnNetworkPortButtonPressed;
|
||||
|
||||
private void NfInitialize()
|
||||
@@ -23,8 +25,7 @@ namespace Content.Client.Shuttles.UI
|
||||
MaximumIFFDistanceValue.OnValueChanged += args => OnRangeFilterChanged(args);
|
||||
|
||||
// Frontier - Maximum Shuttle Speed
|
||||
MaximumShuttleSpeedValue.GetChild(0).GetChild(1).Margin = new Thickness(8, 0, 0, 0);
|
||||
MaximumShuttleSpeedValue.OnValueChanged += args => OnMaxSpeedChanged(args);
|
||||
MaximumShuttleSpeedValue.OnTextChanged += args => OnMaxSpeedChanged(args);
|
||||
|
||||
DampenerOff.OnPressed += _ => SetDampenerMode(InertiaDampeningMode.Off);
|
||||
DampenerOn.OnPressed += _ => SetDampenerMode(InertiaDampeningMode.Dampen);
|
||||
@@ -99,10 +100,11 @@ namespace Content.Client.Shuttles.UI
|
||||
}
|
||||
|
||||
// Frontier - Maximum Shuttle Speed
|
||||
private void OnMaxSpeedChanged(int value)
|
||||
private void OnMaxSpeedChanged(LineEdit.LineEditEventArgs value)
|
||||
{
|
||||
_entManager.TryGetNetEntity(_shuttleEntity, out var shuttle);
|
||||
OnMaxShuttleSpeedChanged?.Invoke(shuttle, value);
|
||||
MaximumShuttleSpeedValue.Text = Regex.Replace(MaximumShuttleSpeedValue.Text, "[^0-9]", "");
|
||||
float.TryParse(MaximumShuttleSpeedValue.Text, out var speed);
|
||||
OnMaxShuttleSpeedChanged?.Invoke(MaximumShuttleSpeedValue.Text == "" ? null : speed);
|
||||
}
|
||||
|
||||
private void NfAddShuttleDesignation(EntityUid? shuttle)
|
||||
@@ -111,11 +113,11 @@ namespace Content.Client.Shuttles.UI
|
||||
if (_entManager.TryGetComponent<MetaDataComponent>(shuttle, out var metadata))
|
||||
{
|
||||
var shipName = metadata.EntityName;
|
||||
|
||||
|
||||
// Try to find a designation in the format XXX-### (like CIV-748)
|
||||
// by checking each word in the ship name
|
||||
var shipNameParts = shipName.Split(' ');
|
||||
|
||||
|
||||
foreach (var part in shipNameParts)
|
||||
{
|
||||
// Check if this part matches the designation format (e.g., CIV-748)
|
||||
@@ -132,7 +134,7 @@ namespace Content.Client.Shuttles.UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we get here, no designation was found, so just show the full name
|
||||
NavDisplayLabel.Text = shipName;
|
||||
// Leave ShuttleDesignation.Text as "Unknown" (the default)
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Content.Client.Shuttles.UI
|
||||
public sealed partial class ShuttleConsoleWindow
|
||||
{
|
||||
public event Action<NetEntity?, InertiaDampeningMode>? OnInertiaDampeningModeChanged;
|
||||
public event Action<NetEntity?, float>? OnMaxShuttleSpeedChanged;
|
||||
public event Action<float?>? OnMaxShuttleSpeedChanged;
|
||||
public event Action<string, string>? OnNetworkPortButtonPressed;
|
||||
|
||||
private void NfInitialize()
|
||||
@@ -18,11 +18,11 @@ namespace Content.Client.Shuttles.UI
|
||||
OnInertiaDampeningModeChanged?.Invoke(entity, mode);
|
||||
};
|
||||
|
||||
NavContainer.OnMaxShuttleSpeedChanged += (entityUid, maxSpeed) =>
|
||||
NavContainer.OnMaxShuttleSpeedChanged += (maxSpeed) =>
|
||||
{
|
||||
OnMaxShuttleSpeedChanged?.Invoke(entityUid, maxSpeed);
|
||||
OnMaxShuttleSpeedChanged?.Invoke(maxSpeed);
|
||||
};
|
||||
|
||||
|
||||
NavContainer.OnNetworkPortButtonPressed += (sourcePort, targetPort) =>
|
||||
{
|
||||
OnNetworkPortButtonPressed?.Invoke(sourcePort, targetPort);
|
||||
|
||||
+2176
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CompanyMembers2 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+2176
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CompanyMembers : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "company_members",
|
||||
columns: table => new
|
||||
{
|
||||
player_user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
company_id = table.Column<string>(type: "text", nullable: false),
|
||||
owner = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_company_members", x => new { x.player_user_id, x.company_id });
|
||||
table.ForeignKey(
|
||||
name: "FK_company_members_player_player_user_id",
|
||||
column: x => x.player_user_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "company_members");
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-1
@@ -20,7 +20,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.1")
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -581,6 +581,26 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("blacklist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerUserId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_user_id");
|
||||
|
||||
b.Property<string>("CompanyId")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("company_id");
|
||||
|
||||
b.Property<bool>("Owner")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("owner");
|
||||
|
||||
b.HasKey("PlayerUserId", "CompanyId")
|
||||
.HasName("PK_company_members");
|
||||
|
||||
b.ToTable("company_members", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1680,6 +1700,19 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("CompanyMembers")
|
||||
.HasForeignKey("PlayerUserId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_company_members_player_player_user_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Server", "Server")
|
||||
@@ -2080,6 +2113,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("CompanyMembers");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
});
|
||||
|
||||
|
||||
+2100
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CompanyMembers : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "company_members",
|
||||
columns: table => new
|
||||
{
|
||||
player_user_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
company_id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
owner = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_company_members", x => new { x.player_user_id, x.company_id });
|
||||
table.ForeignKey(
|
||||
name: "FK_company_members_player_player_user_id",
|
||||
column: x => x.player_user_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "company_members");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Admin", b =>
|
||||
{
|
||||
@@ -550,6 +550,26 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("blacklist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerUserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_user_id");
|
||||
|
||||
b.Property<string>("CompanyId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("company_id");
|
||||
|
||||
b.Property<bool>("Owner")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("owner");
|
||||
|
||||
b.HasKey("PlayerUserId", "CompanyId")
|
||||
.HasName("PK_company_members");
|
||||
|
||||
b.ToTable("company_members", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1604,6 +1624,19 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.CompanyMember", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("CompanyMembers")
|
||||
.HasForeignKey("PlayerUserId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_company_members_player_player_user_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Server", "Server")
|
||||
@@ -1632,7 +1665,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
b1.HasKey("ConnectionLogId");
|
||||
|
||||
b1.ToTable("connection_log");
|
||||
b1.ToTable("connection_log", (string)null);
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ConnectionLogId")
|
||||
@@ -1677,7 +1710,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
b1.HasKey("PlayerId");
|
||||
|
||||
b1.ToTable("player");
|
||||
b1.ToTable("player", (string)null);
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("PlayerId")
|
||||
@@ -1800,7 +1833,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
b1.HasKey("ServerBanId");
|
||||
|
||||
b1.ToTable("server_ban");
|
||||
b1.ToTable("server_ban", (string)null);
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerBanId")
|
||||
@@ -1877,7 +1910,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
b1.HasKey("ServerRoleBanId");
|
||||
|
||||
b1.ToTable("server_role_ban");
|
||||
b1.ToTable("server_role_ban", (string)null);
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerRoleBanId")
|
||||
@@ -2004,6 +2037,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("CompanyMembers");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
});
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Content.Server.Database
|
||||
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
||||
public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
|
||||
public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
|
||||
public DbSet<CompanyMember> CompanyMembers { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -371,6 +372,14 @@ namespace Content.Server.Database
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
|
||||
// Mono
|
||||
modelBuilder.Entity<CompanyMember>()
|
||||
.HasOne(w => w.Player)
|
||||
.WithMany(p => p.CompanyMembers)
|
||||
.HasForeignKey(w => w.PlayerUserId)
|
||||
.HasPrincipalKey(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||
@@ -600,6 +609,7 @@ namespace Content.Server.Database
|
||||
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
||||
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
||||
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
||||
public List<CompanyMember> CompanyMembers { get; set; } = null!; // Mono
|
||||
}
|
||||
|
||||
[Table("whitelist")]
|
||||
@@ -1337,4 +1347,18 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
public float Score { get; set; }
|
||||
}
|
||||
|
||||
// Mono-Start
|
||||
[PrimaryKey(nameof(PlayerUserId), nameof(CompanyId))]
|
||||
public class CompanyMember
|
||||
{
|
||||
[Required, ForeignKey("Player")]
|
||||
public Guid PlayerUserId { get; set; }
|
||||
public Player Player { get; set; } = default!;
|
||||
public bool Owner { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
public string CompanyId { get; set; } = default!;
|
||||
}
|
||||
// Mono-End
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// This is intended so that matches & candles can re-use code for un-shaded layers on in-hand sprites.
|
||||
// However, this could cause conflicts if something is ACTUALLY both a toggleable light and flammable.
|
||||
// if that ever happens, then fire visuals will need to implement their own in-hand sprite management.
|
||||
_appearance.SetData(uid, ToggleableLightVisuals.Enabled, flammable.OnFire, appearance);
|
||||
_appearance.SetData(uid, ToggleableVisuals.Enabled, flammable.OnFire, appearance);
|
||||
}
|
||||
|
||||
public void AdjustFireStacks(EntityUid uid, float relativeFireStacks, FlammableComponent? flammable = null, bool ignite = false)
|
||||
|
||||
@@ -6,8 +6,10 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server._Mono.Company;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared._Mono.Company;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Ghost.Roles;
|
||||
@@ -1940,6 +1942,120 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
|
||||
#endregion
|
||||
|
||||
// Mono
|
||||
#region Company
|
||||
|
||||
public async Task<bool> AddCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var exists = await db.DbContext.CompanyMembers
|
||||
.Where(w => w.PlayerUserId == player)
|
||||
.Where(w => w.CompanyId == company.Id)
|
||||
.AnyAsync();
|
||||
|
||||
if (exists)
|
||||
return false;
|
||||
|
||||
var member = new CompanyMember
|
||||
{
|
||||
PlayerUserId = player,
|
||||
CompanyId = company,
|
||||
};
|
||||
db.DbContext.CompanyMembers.Add(member);
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetPlayerCompanies(Guid player, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
return await db.DbContext.CompanyMembers
|
||||
.Where(w => w.PlayerUserId == player)
|
||||
.Select(w => w.CompanyId)
|
||||
.ToListAsync(cancel);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CompanyMemberRecord>> GetCompanyMembers(ProtoId<CompanyPrototype> company, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
var members = await db.DbContext.CompanyMembers
|
||||
.Where(w => w.CompanyId == company.Id)
|
||||
.Include(c => c.Player)
|
||||
.ToListAsync(cancel);
|
||||
|
||||
return members.Select(m => new CompanyMemberRecord()
|
||||
{
|
||||
Company = company,
|
||||
Owner = m.Owner,
|
||||
PlayerUserId = m.PlayerUserId,
|
||||
LastSeenUserName = m.Player.LastSeenUserName,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CompanyMemberRecord>> GetAllCompanyMembers(CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
var members = await db.DbContext.CompanyMembers
|
||||
.Include(c => c.Player)
|
||||
.ToListAsync(cancel);
|
||||
|
||||
return members.Select(m => new CompanyMemberRecord()
|
||||
{
|
||||
Company = m.CompanyId,
|
||||
Owner = m.Owner,
|
||||
PlayerUserId = m.PlayerUserId,
|
||||
LastSeenUserName = m.Player.LastSeenUserName,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<CompanyMemberRecord?> GetCompanyMember(ProtoId<CompanyPrototype> company, Guid player, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
var member = await db.DbContext.CompanyMembers
|
||||
.Where(w => w.CompanyId == company.Id)
|
||||
.Where(w => w.PlayerUserId == player)
|
||||
.Include(c => c.Player)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (member == null)
|
||||
return null;
|
||||
|
||||
return new CompanyMemberRecord()
|
||||
{
|
||||
Company = company,
|
||||
LastSeenUserName = member.Player.LastSeenUserName,
|
||||
Owner = member.Owner,
|
||||
PlayerUserId = member.PlayerUserId,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task SetCompanyOwner(ProtoId<CompanyPrototype> company, Guid player, bool owner)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
await db.DbContext.CompanyMembers
|
||||
.Where(w => w.CompanyId == company.Id)
|
||||
.Where(w => w.PlayerUserId == player)
|
||||
.ExecuteUpdateAsync(setters => setters.SetProperty(m => m.Owner, owner));
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var entry = await db.DbContext.CompanyMembers
|
||||
.Where(w => w.PlayerUserId == player)
|
||||
.Where(w => w.CompanyId == company.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (entry == null)
|
||||
return false;
|
||||
|
||||
db.DbContext.CompanyMembers.Remove(entry);
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public abstract Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
||||
|
||||
@@ -22,6 +22,8 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using LogLevel = Robust.Shared.Log.LogLevel;
|
||||
using MSLogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
using Content.Shared._Mono.Company;
|
||||
using Content.Server._Mono.Company; // Mono
|
||||
|
||||
namespace Content.Server.Database
|
||||
{
|
||||
@@ -351,6 +353,19 @@ namespace Content.Server.Database
|
||||
|
||||
#endregion
|
||||
|
||||
// Mono
|
||||
#region Company
|
||||
Task AddCompanyMember(Guid player, ProtoId<CompanyPrototype> company);
|
||||
|
||||
Task<List<string>> GetPlayerCompanies(Guid player, CancellationToken cancel = default);
|
||||
Task<IEnumerable<CompanyMemberRecord>> GetCompanyMembers(ProtoId<CompanyPrototype> company, CancellationToken cancel = default);
|
||||
Task<IEnumerable<CompanyMemberRecord>> GetAllCompanyMembers(CancellationToken cancel = default);
|
||||
Task<CompanyMemberRecord?> GetCompanyMember(ProtoId<CompanyPrototype> company, Guid player, CancellationToken cancel = default);
|
||||
Task SetCompanyOwner(ProtoId<CompanyPrototype> company, Guid player, bool owner);
|
||||
Task<bool> RemoveCompanyMember(Guid player, ProtoId<CompanyPrototype> company);
|
||||
|
||||
#endregion
|
||||
|
||||
#region DB Notifications
|
||||
|
||||
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
||||
@@ -1058,7 +1073,7 @@ namespace Content.Server.Database
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
|
||||
}
|
||||
|
||||
|
||||
// Frontier: ghost role DB ops
|
||||
public Task AddGhostRoleWhitelist(Guid player, ProtoId<GhostRolePrototype> ghostRole)
|
||||
{
|
||||
@@ -1095,6 +1110,50 @@ namespace Content.Server.Database
|
||||
return RunDbCommand(() => _db.CleanIPIntelCache(range));
|
||||
}
|
||||
|
||||
// Mono-Start
|
||||
public Task AddCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.AddCompanyMember(player, company));
|
||||
}
|
||||
|
||||
public Task<List<string>> GetPlayerCompanies(Guid player, CancellationToken cancel = default)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetPlayerCompanies(player, cancel));
|
||||
}
|
||||
|
||||
public Task<IEnumerable<CompanyMemberRecord>> GetCompanyMembers(ProtoId<CompanyPrototype> company, CancellationToken cancel = default)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetCompanyMembers(company, cancel));
|
||||
}
|
||||
|
||||
public Task<IEnumerable<CompanyMemberRecord>> GetAllCompanyMembers(CancellationToken cancel = default)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetAllCompanyMembers(cancel));
|
||||
}
|
||||
|
||||
public Task<CompanyMemberRecord?> GetCompanyMember(ProtoId<CompanyPrototype> company, Guid player, CancellationToken cancel = default)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetCompanyMember(company, player, cancel));
|
||||
}
|
||||
|
||||
public Task SetCompanyOwner(ProtoId<CompanyPrototype> company, Guid player, bool owner)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SetCompanyOwner(company, player, owner));
|
||||
}
|
||||
|
||||
public Task<bool> RemoveCompanyMember(Guid player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.RemoveCompanyMember(player, company));
|
||||
}
|
||||
// Mono-End
|
||||
|
||||
public void SubscribeToNotifications(Action<DatabaseNotification> handler)
|
||||
{
|
||||
lock (_notificationHandlers)
|
||||
|
||||
@@ -44,10 +44,15 @@ namespace Content.Server.Destructible
|
||||
[Dependency] public readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] public readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
// Mono
|
||||
private EntityQuery<DestructibleComponent> _destructibleQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DestructibleComponent, DamageChangedEvent>(Execute);
|
||||
|
||||
_destructibleQuery = GetEntityQuery<DestructibleComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -131,7 +136,7 @@ namespace Content.Server.Destructible
|
||||
public bool TryGetDestroyedAt(Entity<DestructibleComponent?> ent, [NotNullWhen(true)] out FixedPoint2? destroyedAt)
|
||||
{
|
||||
destroyedAt = null;
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
if (!_destructibleQuery.TryComp(ent, out ent.Comp)) // Mono
|
||||
return false;
|
||||
|
||||
destroyedAt = DestroyedAt(ent, ent.Comp);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._Mono.Company; // Mono
|
||||
using Content.Server._NF.Auth;
|
||||
using Content.Server.Acz;
|
||||
using Content.Server.Administration;
|
||||
@@ -155,6 +156,7 @@ namespace Content.Server.Entry
|
||||
|
||||
_euiManager.Initialize();
|
||||
|
||||
IoCManager.Resolve<CompanyManager>().Initialize(); // Mono
|
||||
IoCManager.Resolve<IGameMapManager>().Initialize();
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
||||
IoCManager.Resolve<IBanManager>().Initialize();
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// calls the trigger when the object is initialized
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TriggerOnSpawnComponent : Component
|
||||
namespace Content.Server.Explosion.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// calls the trigger when the object is initialized
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TriggerOnSpawnComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts a timer on spawn rather than instantly triggering
|
||||
/// </summary>
|
||||
[DataField("timerOnly")] // Mono: Added "timerOnly" boolean
|
||||
public bool timerOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public sealed partial class ExplosionSystem
|
||||
|
||||
// As above, but for space-based explosion propagating from space onto grids.
|
||||
HashSet<EntityUid> encounteredGrids = new();
|
||||
Dictionary<EntityUid, HashSet<Vector2i>>? previousGridJump;
|
||||
// Dictionary<EntityUid, HashSet<Vector2i>>? previousGridJump; // Mono
|
||||
|
||||
// variables for transforming between grid and space-coordinates
|
||||
var spaceMatrix = Matrix3x2.Identity;
|
||||
@@ -174,14 +174,27 @@ public sealed partial class ExplosionSystem
|
||||
|
||||
// In order to treat "cost" of moving off a grid on the same level as moving onto a grid, both space -> grid and grid -> space have to be delayed by one iteration.
|
||||
previousSpaceJump = spaceJump;
|
||||
previousGridJump = spaceData?.GridJump;
|
||||
spaceJump = new();
|
||||
|
||||
var newTileCount = 0;
|
||||
|
||||
if (previousGridJump != null)
|
||||
encounteredGrids.UnionWith(previousGridJump.Keys);
|
||||
// Mono - explosion fix, feat. gemini from googer
|
||||
// 1. Process space expansion FIRST.
|
||||
// If space-data is null, but some grid-based explosion reached space, we need to initialize it.
|
||||
if (spaceData == null && previousSpaceJump.Count != 0)
|
||||
spaceData = new ExplosionSpaceTileFlood(this, epicenter, referenceGrid, localGrids, maxDistance);
|
||||
|
||||
// If the explosion has reached space, do that neighbors finding step as well.
|
||||
if (spaceData != null)
|
||||
newTileCount += spaceData.AddNewTiles(iteration, previousSpaceJump);
|
||||
|
||||
// 2. Feed the new grid jumps immediately into THIS iteration (0 delay for Space -> Grid transitions)
|
||||
var currentGridJump = spaceData?.GridJump;
|
||||
|
||||
if (currentGridJump != null)
|
||||
encounteredGrids.UnionWith(currentGridJump.Keys);
|
||||
|
||||
// 3. Process grids and immediate space->grid jumps
|
||||
foreach (var grid in encounteredGrids)
|
||||
{
|
||||
// is this a new grid, for which we must create a new explosion data set
|
||||
@@ -205,18 +218,10 @@ public sealed partial class ExplosionSystem
|
||||
}
|
||||
|
||||
// get the new neighbours, and populate gridToSpaceTiles in the process.
|
||||
newTileCount += data.AddNewTiles(iteration, previousGridJump?.GetValueOrDefault(grid));
|
||||
newTileCount += data.AddNewTiles(iteration, currentGridJump?.GetValueOrDefault(grid));
|
||||
spaceJump.UnionWith(data.SpaceJump);
|
||||
}
|
||||
|
||||
// if space-data is null, but some grid-based explosion reached space, we need to initialize it.
|
||||
if (spaceData == null && previousSpaceJump.Count != 0)
|
||||
spaceData = new ExplosionSpaceTileFlood(this, epicenter, referenceGrid, localGrids, maxDistance);
|
||||
|
||||
// If the explosion has reached space, do that neighbors finding step as well.
|
||||
if (spaceData != null)
|
||||
newTileCount += spaceData.AddNewTiles(iteration, previousSpaceJump);
|
||||
|
||||
// Does adding these tiles bring us above the total target intensity?
|
||||
tilesInIteration.Add(newTileCount);
|
||||
if (newTileCount * stepSize >= remainingIntensity)
|
||||
|
||||
@@ -303,7 +303,10 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
|
||||
private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
|
||||
{
|
||||
Trigger(uid);
|
||||
if (component.timerOnly == true)
|
||||
StartTimer(uid,uid);
|
||||
else
|
||||
Trigger(uid);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args)
|
||||
|
||||
@@ -85,6 +85,9 @@ public sealed class SmokeSystem : EntitySystem
|
||||
if (_smokeAffectedQuery.HasComponent(args.OtherEntity))
|
||||
return;
|
||||
|
||||
if (TerminatingOrDeleted(args.OtherEntity)) // Mono: Testfail fix
|
||||
return;
|
||||
|
||||
var smokeAffected = AddComp<SmokeAffectedComponent>(args.OtherEntity);
|
||||
smokeAffected.SmokeEntity = entity;
|
||||
smokeAffected.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
|
||||
@@ -104,6 +107,9 @@ public sealed class SmokeSystem : EntitySystem
|
||||
if (!TryComp<PhysicsComponent>(args.OtherEntity, out var body))
|
||||
return;
|
||||
|
||||
if (TerminatingOrDeleted(args.OtherEntity)) // Mono: Testfail fix
|
||||
return;
|
||||
|
||||
foreach (var ent in _physics.GetContactingEntities(args.OtherEntity, body))
|
||||
{
|
||||
if (exists && ent == entity.Owner)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._Mono.Company; // Mono
|
||||
using Content.Server._NF.Auth;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Logs;
|
||||
@@ -78,6 +79,7 @@ namespace Content.Server.IoC
|
||||
IoCManager.Register<MultiServerKickManager>();
|
||||
IoCManager.Register<CVarControlManager>();
|
||||
IoCManager.Register<MiniAuthManager>(); //Frontier
|
||||
IoCManager.Register<CompanyManager>(); // Mono
|
||||
|
||||
IoCManager.Register<DiscordLink>();
|
||||
IoCManager.Register<DiscordChatLink>();
|
||||
|
||||
@@ -17,5 +17,11 @@ public sealed partial class LatheProducingComponent : Component
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan ProductionLength;
|
||||
|
||||
/// <summary>
|
||||
/// The Entity that queued this recipe - Mono
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? Actor;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace Content.Server.Lathe
|
||||
[Dependency] private readonly ReagentSpeedSystem _reagentSpeed = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly ContrabandTurnInSystem _contraband = default!; // Mono
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly DeviceLinkSystem _deviceLink = default!; // Mono
|
||||
|
||||
@@ -225,7 +226,7 @@ namespace Content.Server.Lathe
|
||||
}
|
||||
|
||||
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, int quantity, LatheComponent? component = null, // Frontier: add quantity
|
||||
bool canDebt = false) // Mono
|
||||
EntityUid? actor = null, bool canDebt = false) // Mono
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
@@ -243,7 +244,8 @@ namespace Content.Server.Lathe
|
||||
if (component.Queue.Count > 0 && component.Queue[^1].Recipe.ID == recipe.ID)
|
||||
component.Queue[^1].ItemsRequested += quantity;
|
||||
else
|
||||
component.Queue.Add(new LatheRecipeBatch(recipe, 0, quantity));
|
||||
component.Queue.Add(new LatheRecipeBatch(recipe, 0, quantity,
|
||||
GetNetEntity(actor))); // Mono: Adds actor
|
||||
// End Frontier
|
||||
// component.Queue.Add(recipe); // Frontier
|
||||
|
||||
@@ -264,6 +266,7 @@ namespace Content.Server.Lathe
|
||||
|
||||
// Frontier: handle batches
|
||||
var batch = component.Queue.First();
|
||||
var actor = batch.Actor; // Mono: Adds actor
|
||||
var recipe = batch.Recipe;
|
||||
// <Mono> - resources now consumed as the production goes
|
||||
if (!CanProduce(uid, recipe, 1, component))
|
||||
@@ -296,6 +299,7 @@ namespace Content.Server.Lathe
|
||||
var lathe = EnsureComp<LatheProducingComponent>(uid);
|
||||
lathe.StartTime = _timing.CurTime;
|
||||
lathe.ProductionLength = time * component.FinalTimeMultiplier; // Frontier: TimeMultiplier<FinalTimeMultiplier
|
||||
lathe.Actor = GetEntity(actor);
|
||||
component.CurrentRecipe = recipe;
|
||||
|
||||
var ev = new LatheStartPrintingEvent(recipe);
|
||||
@@ -325,8 +329,13 @@ namespace Content.Server.Lathe
|
||||
|
||||
// Frontier: adjust price before merge (stack prices changed once)
|
||||
if (result.Valid)
|
||||
{
|
||||
ModifyPrintedEntityPrice(uid, comp, result);
|
||||
// End Frontier
|
||||
// End Frontier
|
||||
|
||||
// Mono: Handle printable contraband
|
||||
_contraband.HandleContrabandValueByCompany(result, prodComp.Actor);
|
||||
}
|
||||
|
||||
_stack.TryMergeToContacts(result);
|
||||
}
|
||||
@@ -355,8 +364,9 @@ namespace Content.Server.Lathe
|
||||
}
|
||||
|
||||
// <Mono>
|
||||
// Add the actor that previously queued to looped items
|
||||
if (comp.Loop)
|
||||
TryAddToQueue(uid, comp.CurrentRecipe, 1, comp, true);
|
||||
TryAddToQueue(uid, comp.CurrentRecipe, 1, comp, prodComp.Actor, true);
|
||||
|
||||
_deviceLink.SendSignal(uid, comp.ProducedPort, true);
|
||||
// </Mono>
|
||||
@@ -493,7 +503,7 @@ namespace Content.Server.Lathe
|
||||
if (_proto.TryIndex(args.ID, out LatheRecipePrototype? recipe))
|
||||
{
|
||||
// Frontier: batching recipes
|
||||
if (TryAddToQueue(uid, recipe, args.Quantity, component))
|
||||
if (TryAddToQueue(uid, recipe, args.Quantity, component, args.Actor))
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
|
||||
@@ -121,8 +121,14 @@ public sealed partial class MechSystem : SharedMechSystem
|
||||
if (!TryComp<BatteryComponent>(ent.Comp.BatterySlot.ContainedEntity, out var battery))
|
||||
return;
|
||||
|
||||
ent.Comp.CriticalPowerState = battery.CurrentCharge / battery.MaxCharge <= 0.05f;
|
||||
//ent.Comp.CriticalPowerState = battery.CurrentCharge / battery.MaxCharge <= 0.05f;
|
||||
// Mono
|
||||
var criticalState = battery.CurrentCharge / battery.MaxCharge <= 0.05f;
|
||||
if (criticalState == ent.Comp.CriticalPowerState)
|
||||
return;
|
||||
|
||||
ent.Comp.CriticalPowerState = criticalState;
|
||||
// End Mono
|
||||
Dirty(ent);
|
||||
_movementSpeed.RefreshMovementSpeedModifiers(ent);
|
||||
}
|
||||
|
||||
@@ -35,12 +35,13 @@ public sealed class JetpackSystem : SharedJetpackSystem
|
||||
/// </summary>
|
||||
private void OnJetpackActivated(EntityUid uid, ActiveJetpackComponent component, ComponentStartup args)
|
||||
{
|
||||
if (TryComp<JetpackComponent>(uid, out var jetpack) && jetpack.Stealth == false)
|
||||
if (TryComp<JetpackComponent>(uid, out var jetpack) && jetpack.DetectionRange > 0)
|
||||
{
|
||||
var blip = EnsureComp<RadarBlipComponent>(uid);
|
||||
blip.Config.Color = Color.Cyan;
|
||||
blip.Config.Bounds = new(-0.25f, -0.25f, 0.25f, 0.25f);
|
||||
blip.VisibleFromOtherGrids = true;
|
||||
blip.MaxDistance = jetpack.DetectionRange;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ public sealed partial class NPCRangedCombatComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ShootAccumulator;
|
||||
|
||||
/// <summary>
|
||||
/// Mono - accept having to fire for up to this many seconds to destroy an obstacle.
|
||||
/// </summary>
|
||||
public float ShotsThreshold = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play if the target enters line of sight.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Mono - whole file
|
||||
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
/// <summary>
|
||||
/// Identical to NPCCombatSystem.Ranged LOS check.
|
||||
/// </summary>
|
||||
public sealed partial class GunTargetGoodCon : UtilityConsideration
|
||||
{
|
||||
[DataField]
|
||||
public float ShootThroughThreshold = 2f;
|
||||
}
|
||||
// TODO: rewrite HTN and make this not evil
|
||||
@@ -0,0 +1,10 @@
|
||||
// Mono - whole file
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
/// <summary>
|
||||
/// Returns 1f if the target is alive or can't be alive, or 0f if not.
|
||||
/// </summary>
|
||||
public sealed partial class TargetIsAliveOrNACon : UtilityConsideration
|
||||
{
|
||||
|
||||
}
|
||||
@@ -6,12 +6,12 @@ using Content.Shared.Whitelist;
|
||||
namespace Content.Server.NPC.Queries.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Returns nearby shuttles considered hostile from <see cref="FactionSystem"/>
|
||||
/// Returns nearby entities tagged with ShipNpcTargetComponent.
|
||||
/// </summary>
|
||||
public sealed partial class NearbyHostileShuttlesQuery : UtilityQuery
|
||||
public sealed partial class NearbyNpcTargetsQuery : UtilityQuery
|
||||
{
|
||||
[DataField]
|
||||
public float Range = 2000f;
|
||||
public float Range = 4000f;
|
||||
|
||||
// TODO: make this use factions
|
||||
[DataField]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.NPC.Queries.Considerations;
|
||||
using Content.Server.NPC.Queries.Queries;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; // Mono
|
||||
|
||||
namespace Content.Server.NPC.Queries;
|
||||
|
||||
@@ -10,12 +11,22 @@ namespace Content.Server.NPC.Queries;
|
||||
/// These results are then run through the considerations.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class UtilityQueryPrototype : IPrototype
|
||||
public sealed partial class UtilityQueryPrototype : IPrototype, IInheritingPrototype // Mono
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("query")]
|
||||
// <Mono>
|
||||
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<UtilityQueryPrototype>))]
|
||||
public string[]? Parents { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[NeverPushInheritance]
|
||||
[AbstractDataField]
|
||||
public bool Abstract { get; private set; }
|
||||
// </Mono>
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("query"), AlwaysPushInheritance]
|
||||
public List<UtilityQuery> Query = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("considerations")]
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Destructible; // Mono
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Shared._Goobstation.Weapons.SmartGun;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.NPC.Components; // Mono
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
@@ -18,6 +20,7 @@ public sealed partial class NPCCombatSystem
|
||||
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotate = default!;
|
||||
[Dependency] private readonly SharedLaserPointerSystem _pointer = default!; // Goobstation
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
|
||||
private EntityQuery<CombatModeComponent> _combatQuery;
|
||||
private EntityQuery<NPCSteeringComponent> _steeringQuery;
|
||||
@@ -25,6 +28,7 @@ public sealed partial class NPCCombatSystem
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<RequireProjectileTargetComponent> _requireTargetQuery; // Mono
|
||||
private EntityQuery<NpcFactionMemberComponent> _factionQuery; // Mono
|
||||
|
||||
// TODO: Don't predict for hitscan
|
||||
private const float ShootSpeed = 20f;
|
||||
@@ -42,6 +46,7 @@ public sealed partial class NPCCombatSystem
|
||||
_steeringQuery = GetEntityQuery<NPCSteeringComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_requireTargetQuery = GetEntityQuery<RequireProjectileTargetComponent>(); // Mono
|
||||
_factionQuery = GetEntityQuery<NpcFactionMemberComponent>(); // Mono
|
||||
|
||||
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentStartup>(OnRangedStartup);
|
||||
SubscribeLocalEvent<NPCRangedCombatComponent, ComponentShutdown>(OnRangedShutdown);
|
||||
@@ -74,10 +79,12 @@ public sealed partial class NPCCombatSystem
|
||||
|
||||
private void UpdateRanged(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<NPCRangedCombatComponent, TransformComponent>();
|
||||
var query = EntityQueryEnumerator<NPCRangedCombatComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
var xform = Transform(uid); // Mono
|
||||
|
||||
if (!_gun.TryGetGun(uid, out var gunUid, out var gun))
|
||||
{
|
||||
comp.Status = CombatStatus.NoWeapon;
|
||||
@@ -152,12 +159,8 @@ public sealed partial class NPCCombatSystem
|
||||
if (comp.LOSAccumulator < 0f)
|
||||
{
|
||||
comp.LOSAccumulator += UnoccludedCooldown;
|
||||
// For consistency with NPC steering. // Mono
|
||||
comp.TargetInLOS = _interaction.InRangeUnobstructed(uid, comp.Target, distance + 0.1f, comp.ObstructedMask, predicate: (EntityUid entity) =>
|
||||
{
|
||||
return _physicsQuery.TryGetComponent(entity, out var physics) && (physics.CollisionLayer & (int)comp.BulletMask) == 0 // ignore if it can't collide with bullets
|
||||
|| _requireTargetQuery.HasComponent(entity); // or if it requires targeting
|
||||
});
|
||||
// Mono
|
||||
comp.TargetInLOS = InRangeGoodTarget((gunUid, gun), uid, comp.Target, distance, comp.ShotsThreshold, comp.ObstructedMask, comp.BulletMask);
|
||||
}
|
||||
|
||||
if (!comp.TargetInLOS)
|
||||
@@ -265,4 +268,37 @@ public sealed partial class NPCCombatSystem
|
||||
target);
|
||||
}
|
||||
}
|
||||
|
||||
// Mono
|
||||
public bool InRangeGoodTarget(Entity<GunComponent?> gun, EntityUid source, EntityUid target, float distance, float shotsThreshold, CollisionGroup obstructedMask = CollisionGroup.Opaque, CollisionGroup bulletMask = CollisionGroup.Impassable | CollisionGroup.BulletImpassable)
|
||||
{
|
||||
if (!Resolve(gun, ref gun.Comp, false))
|
||||
return false;
|
||||
|
||||
var dmg = _gun.GetNextDamage(gun).GetTotal().Float(); // this will be wrong but damageable is evil and i have no idea how to do this better
|
||||
var destroyAccum = 0f;
|
||||
var firerate = gun.Comp.FireRateModified;
|
||||
var threshold = shotsThreshold;
|
||||
// For consistency with NPC steering. // Mono
|
||||
return _interaction.InRangeUnobstructed(source, target, distance + 0.1f, obstructedMask,
|
||||
predicate: (EntityUid uid) => {
|
||||
if (_physicsQuery.TryGetComponent(uid, out var physics)
|
||||
&& (physics.CollisionLayer & (int)bulletMask) == 0
|
||||
) // this will collide with the bullet
|
||||
return true;
|
||||
|
||||
if (_requireTargetQuery.HasComponent(uid)) // requires targeting so it won't collide
|
||||
return true;
|
||||
|
||||
// do not consider living things for shoot-through
|
||||
if (!_factionQuery.HasComp(uid)
|
||||
&& _destructible.TryGetDestroyedAt(uid, out var at))
|
||||
{
|
||||
destroyAccum += at.Value.Float() / dmg / firerate;
|
||||
if (destroyAccum < threshold)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Destructible; // Mono
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server._Mono.NPC.HTN; // Mono
|
||||
using Content.Server.NPC.Queries;
|
||||
@@ -12,6 +13,7 @@ using Content.Server.Shuttles.Components; // Mono
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
@@ -19,16 +21,20 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC.Components; // Mono
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Server.Weapons.Ranged.Systems; // Mono
|
||||
using Content.Shared.Whitelist;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Physics.Components; // Mono
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
@@ -58,9 +64,15 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; // Frontier
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!; // Mono
|
||||
[Dependency] private readonly GunSystem _gun = default!; // Mono
|
||||
[Dependency] private readonly NPCCombatSystem _npcCombat = default!;
|
||||
|
||||
private EntityQuery<PuddleComponent> _puddleQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery; // Mono
|
||||
private EntityQuery<RequireProjectileTargetComponent> _requireTargetQuery; // Mono
|
||||
private EntityQuery<NpcFactionMemberComponent> _factionQuery; // Mono
|
||||
|
||||
private ObjectPool<HashSet<EntityUid>> _entPool =
|
||||
new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), 256);
|
||||
@@ -75,6 +87,9 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
base.Initialize();
|
||||
_puddleQuery = GetEntityQuery<PuddleComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>(); // Mono
|
||||
_requireTargetQuery = GetEntityQuery<RequireProjectileTargetComponent>(); // Mono
|
||||
_factionQuery = GetEntityQuery<NpcFactionMemberComponent>(); // Mono
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -353,6 +368,17 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
|
||||
return _examine.InRangeUnOccluded(owner, targetUid, radius + bufferRange, null) ? 1f : 0f;
|
||||
}
|
||||
// Mono
|
||||
case GunTargetGoodCon con:
|
||||
{
|
||||
if (!_gun.TryGetGun(owner, out var gunUid, out var gun))
|
||||
return 0f;
|
||||
|
||||
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
|
||||
const float bufferRange = 0.5f;
|
||||
|
||||
return _npcCombat.InRangeGoodTarget((gunUid, gun), owner, targetUid, radius + bufferRange, con.ShootThroughThreshold) ? 1f : 0f;
|
||||
}
|
||||
case TargetIsAliveCon:
|
||||
{
|
||||
return _mobState.IsAlive(targetUid) ? 1f : 0f;
|
||||
@@ -390,6 +416,11 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
return _statusEffectsSystem.HasStatusEffect(targetUid, "Stun") ? 1f : 0f;
|
||||
}
|
||||
// End Frontier
|
||||
// Mono
|
||||
case TargetIsAliveOrNACon:
|
||||
{
|
||||
return !TryComp<MobStateComponent>(targetUid, out var mobState) || _mobState.IsAlive(targetUid, mobState) ? 1f : 0f;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -494,7 +525,7 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
break;
|
||||
}
|
||||
// Mono - TODO: consider factions
|
||||
case NearbyHostileShuttlesQuery shuttlesQuery:
|
||||
case NearbyNpcTargetsQuery shuttlesQuery:
|
||||
{
|
||||
var xform = Transform(owner);
|
||||
var ownGrid = xform.GridUid;
|
||||
@@ -502,11 +533,13 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
{
|
||||
var targetXform = Transform(target);
|
||||
var targetGrid = targetXform.GridUid;
|
||||
if (targetComp.NeedGrid && targetGrid == null ||
|
||||
targetGrid == ownGrid ||
|
||||
(_transform.GetWorldPosition(target) - _transform.GetWorldPosition(xform)).Length() > shuttlesQuery.Range ||
|
||||
targetComp.NeedPower && !this.IsPowered(target, EntityManager) ||
|
||||
targetGrid != null && _whitelistSystem.IsBlacklistPass(shuttlesQuery.Blacklist, targetGrid.Value))
|
||||
if (targetComp.NeedGrid != NpcTargetGridMode.Either // if we care about grid..
|
||||
// ..and our (non-)need for grid is equal to the (non-)absence of a grid
|
||||
&& (targetComp.NeedGrid == NpcTargetGridMode.OnGrid) == (targetGrid == null)
|
||||
|| targetGrid == ownGrid
|
||||
|| (_transform.GetWorldPosition(target) - _transform.GetWorldPosition(xform)).Length() > shuttlesQuery.Range
|
||||
|| targetComp.NeedPower && !this.IsPowered(target, EntityManager)
|
||||
|| targetGrid != null && _whitelistSystem.IsBlacklistPass(shuttlesQuery.Blacklist, targetGrid.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public sealed class MoverController : SharedMoverController
|
||||
return;
|
||||
|
||||
args.Input = input;
|
||||
args.SetMaxVelocity = entity.Comp.SetMaxVelocity;
|
||||
}
|
||||
|
||||
private void PilotedShuttleRelayEvent<TEvent>(Entity<PilotedShuttleComponent> entity, ref TEvent args)
|
||||
@@ -273,19 +274,27 @@ public sealed class MoverController : SharedMoverController
|
||||
// Mono: all below code handling shuttle movement has been heavily modified by Monolith
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Get a shuttle's torque.
|
||||
/// </summary>
|
||||
public float GetTorque(ShuttleComponent shuttle)
|
||||
{
|
||||
return shuttle.AngularThrust * shuttle.AngularMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a shuttle's angular acceleration.
|
||||
/// </summary>
|
||||
public float GetAngularAcceleration(ShuttleComponent shuttle, PhysicsComponent body)
|
||||
{
|
||||
return shuttle.AngularThrust * body.InvI;
|
||||
return GetTorque(shuttle) * body.InvI;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get shuttle thrust force in a given direction.
|
||||
/// Takes local direction.
|
||||
/// </summary>
|
||||
public Vector2 GetDirectionThrust(Vector2 dir, ShuttleComponent shuttle)
|
||||
public Vector2 GetDirectionThrust(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
|
||||
{
|
||||
if (dir.Length() == 0f)
|
||||
return Vector2.Zero;
|
||||
@@ -302,28 +311,48 @@ public sealed class MoverController : SharedMoverController
|
||||
// prevent NaNs
|
||||
dir *= dir.X == 0 ? vertScale : dir.Y == 0 ? horizScale : MathF.Min(horizScale, vertScale);
|
||||
|
||||
return dir;
|
||||
}
|
||||
var northAngle = xform.LocalRotation;
|
||||
var localVel = (-northAngle).RotateVec(body.LinearVelocity);
|
||||
|
||||
public Vector2 GetDirectionAccel(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body)
|
||||
{
|
||||
return GetDirectionThrust(dir, shuttle) * body.InvMass;
|
||||
// scale our velocity-wards component by 1 / (vel/basemax + 1)
|
||||
var dot = Vector2.Dot(dir, localVel);
|
||||
if (dot > 0f)
|
||||
{
|
||||
var velLenSq = localVel.LengthSquared();
|
||||
var dirCompVel = localVel * dot / velLenSq;
|
||||
var velRatio = localVel.Length() / shuttle.BaseMaxLinearVelocity;
|
||||
// less effect at lower velocities
|
||||
var exponent = velRatio * MathF.Pow(velRatio / (1f + velRatio), 3f);
|
||||
var scaledComp = dirCompVel / MathF.Pow(2f, exponent);
|
||||
dir = dir - dirCompVel + scaledComp;
|
||||
}
|
||||
|
||||
return dir * shuttle.AccelerationMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to extrapolate max velocity for a given Vector2 (really, its angle) and shuttle.
|
||||
/// Get shuttle acceleration in a given direction.
|
||||
/// Takes local direction.
|
||||
/// </summary>
|
||||
public Vector2 ObtainMaxVel(Vector2 vel, ShuttleComponent shuttle, PhysicsComponent body) // mono
|
||||
public Vector2 GetDirectionAccel(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
|
||||
{
|
||||
if (vel.Length() == 0f)
|
||||
return Vector2.Zero;
|
||||
return GetDirectionThrust(dir, shuttle, body, xform) * body.InvMass;
|
||||
}
|
||||
|
||||
var thrust = GetDirectionThrust(vel, shuttle);
|
||||
var twr = thrust.Length() / body.Mass;
|
||||
var twrMult = MathF.Pow(twr / shuttle.BaseMaxVelocityTWR, shuttle.MaxVelocityScalingExponent);
|
||||
/// <summary>
|
||||
/// Get shuttle thrust force in a given world direction.
|
||||
/// </summary>
|
||||
public Vector2 GetWorldDirectionThrust(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
|
||||
{
|
||||
return xform.LocalRotation.RotateVec(GetDirectionThrust((-xform.LocalRotation).RotateVec(dir), shuttle, body, xform));
|
||||
}
|
||||
|
||||
return vel.Normalized() * MathF.Min(shuttle.BaseMaxLinearVelocity * twrMult, MathF.Min(shuttle.UpperMaxVelocity, shuttle.SetMaxVelocity));
|
||||
/// <summary>
|
||||
/// Get shuttle acceleration in a given world direction.
|
||||
/// </summary>
|
||||
public Vector2 GetWorldDirectionAccel(Vector2 dir, ShuttleComponent shuttle, PhysicsComponent body, TransformComponent xform)
|
||||
{
|
||||
return GetWorldDirectionThrust(dir, shuttle, body, xform) * body.InvMass;
|
||||
}
|
||||
|
||||
private void HandleShuttleMovement(float frameTime)
|
||||
@@ -335,6 +364,9 @@ public sealed class MoverController : SharedMoverController
|
||||
// query all our pilots for input
|
||||
var toRemove = new List<EntityUid>();
|
||||
|
||||
var angularMul = 0f;
|
||||
var accelMul = 0f;
|
||||
var setMaxVel = (float?)0f;
|
||||
foreach (var pilot in piloted.InputSources)
|
||||
{
|
||||
var inputsEv = new GetShuttleInputsEvent(frameTime, uid);
|
||||
@@ -343,7 +375,15 @@ public sealed class MoverController : SharedMoverController
|
||||
if (!inputsEv.GotInput)
|
||||
toRemove.Add(pilot);
|
||||
else if (inputsEv.Input != null)
|
||||
{
|
||||
inputs.Add(inputsEv.Input.Value);
|
||||
angularMul += inputsEv.AngularMul;
|
||||
accelMul += inputsEv.AccelMul;
|
||||
if (setMaxVel != null && inputsEv.SetMaxVelocity != null)
|
||||
setMaxVel += inputsEv.SetMaxVelocity;
|
||||
else
|
||||
setMaxVel = null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var remUid in toRemove)
|
||||
@@ -359,6 +399,7 @@ public sealed class MoverController : SharedMoverController
|
||||
{
|
||||
_thruster.DisableLinearThrusters(shuttle);
|
||||
PhysicsSystem.SetSleepingAllowed(uid, body, true);
|
||||
shuttle.AngularMultiplier = shuttle.AccelerationMultiplier = 1f;
|
||||
continue;
|
||||
}
|
||||
PhysicsSystem.SetSleepingAllowed(uid, body, false);
|
||||
@@ -377,8 +418,17 @@ public sealed class MoverController : SharedMoverController
|
||||
angularInput /= count;
|
||||
brakeInput /= count;
|
||||
|
||||
angularMul /= count;
|
||||
accelMul /= count;
|
||||
if (setMaxVel != null)
|
||||
setMaxVel /= count;
|
||||
shuttle.AngularMultiplier = angularMul;
|
||||
shuttle.AccelerationMultiplier = accelMul;
|
||||
|
||||
var shuttleNorthAngle = _xformSystem.GetWorldRotation(uid);
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
// handle movement: brake
|
||||
if (brakeInput > 0f)
|
||||
{
|
||||
@@ -389,7 +439,7 @@ public sealed class MoverController : SharedMoverController
|
||||
|
||||
// Get velocity relative to the shuttle so we know which thrusters to fire
|
||||
var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
|
||||
var force = GetDirectionThrust(-shuttleVelocity, shuttle);
|
||||
var force = GetDirectionThrust(-shuttleVelocity, shuttle, body, xform);
|
||||
|
||||
if (force.X < 0f)
|
||||
{
|
||||
@@ -473,7 +523,7 @@ public sealed class MoverController : SharedMoverController
|
||||
var linearDir = angle.GetDir();
|
||||
var dockFlag = linearDir.AsFlag();
|
||||
|
||||
var totalForce = GetDirectionThrust(linearInput, shuttle);
|
||||
var totalForce = GetDirectionThrust(linearInput, shuttle, body, xform);
|
||||
|
||||
// Won't just do cardinal directions.
|
||||
foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag)))
|
||||
@@ -497,17 +547,20 @@ public sealed class MoverController : SharedMoverController
|
||||
}
|
||||
|
||||
var localVel = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
|
||||
// vector of max velocity we can be traveling with along current direction
|
||||
var maxVelocity = ObtainMaxVel(localVel, shuttle, body);
|
||||
// vector of max velocity we can be traveling with along wish-direction
|
||||
var maxWishVelocity = ObtainMaxVel(totalForce, shuttle, body);
|
||||
// if we're going faster than we can be, thrust to adjust our velocity to the max wish-direction velocity
|
||||
if (localVel.LengthSquared() > maxVelocity.LengthSquared())
|
||||
if (setMaxVel is { } speed && localVel.LengthSquared() != 0f && totalForce.LengthSquared() != 0f)
|
||||
{
|
||||
var velDelta = maxWishVelocity - localVel;
|
||||
var maxForceLength = velDelta.Length() * body.Mass / frameTime;
|
||||
var appliedLength = MathF.Min(totalForce.Length(), maxForceLength);
|
||||
totalForce = velDelta.Length() == 0 ? Vector2.Zero : velDelta.Normalized() * appliedLength;
|
||||
// vector of max velocity we can be traveling with along current direction
|
||||
var maxVelocity = localVel.Normalized() * speed;
|
||||
// vector of max velocity we can be traveling with along wish-direction
|
||||
var maxWishVelocity = totalForce.Normalized() * speed;
|
||||
// if we're going faster than we can be, thrust to adjust our velocity to the max wish-direction velocity
|
||||
if (localVel.Length() / maxVelocity.Length() > 0.999f)
|
||||
{
|
||||
var velDelta = maxWishVelocity - localVel;
|
||||
var maxForceLength = velDelta.Length() * body.Mass / frameTime;
|
||||
var appliedLength = MathF.Min(totalForce.Length(), maxForceLength);
|
||||
totalForce = velDelta.Length() == 0 ? Vector2.Zero : velDelta.Normalized() * appliedLength;
|
||||
}
|
||||
}
|
||||
|
||||
totalForce = shuttleNorthAngle.RotateVec(totalForce);
|
||||
@@ -524,7 +577,7 @@ public sealed class MoverController : SharedMoverController
|
||||
}
|
||||
else
|
||||
{
|
||||
var torque = shuttle.AngularThrust * -angularInput;
|
||||
var torque = GetTorque(shuttle) * -angularInput;
|
||||
|
||||
// Need to cap the velocity if 1 tick of input brings us over cap so we don't continuously
|
||||
// edge onto the cap over and over.
|
||||
|
||||
@@ -9,9 +9,15 @@ public record struct ShuttleInput(Vector2 Strafe, float Rotation, float Brakes);
|
||||
/// <summary>
|
||||
/// Raised on pilots to get inputs given to a shuttle.
|
||||
/// If GotInput is false, this piloted is removed from input sources.
|
||||
/// Also queries for multipliers to acceleration and max speed.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetShuttleInputsEvent(float FrameTime, EntityUid ShuttleUid, ShuttleInput? Input = null, bool GotInput = false);
|
||||
public record struct GetShuttleInputsEvent(float FrameTime, EntityUid ShuttleUid, ShuttleInput? Input = null, bool GotInput = false)
|
||||
{
|
||||
public float AngularMul = 1f;
|
||||
public float AccelMul = 1f;
|
||||
public float? SetMaxVelocity = null;
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct PilotedShuttleRelayedEvent<TEvent>(TEvent Args);
|
||||
|
||||
@@ -144,7 +144,8 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
continue;
|
||||
|
||||
var xform = Transform(uid);
|
||||
var lastPosition = _transformSystem.GetWorldPosition(xform);
|
||||
var lastMap = _transformSystem.GetMapCoordinates(xform);
|
||||
var lastPosition = lastMap.Position;
|
||||
var rayDirection = currentVelocity / velLen;
|
||||
// Ensure rayDistance is not zero to prevent issues with IntersectRay if frametime or velocity is zero.
|
||||
var rayDistance = velLen * frameTime;
|
||||
@@ -243,9 +244,9 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
return false;
|
||||
|
||||
// teleport us so we hit it
|
||||
// this is cursed but i don't think there's a better way to force a collision here
|
||||
var hitXform = Transform(minHit.Uid.Value);
|
||||
var hitPos = hitXform.Coordinates;
|
||||
var hitMapCoord = lastMap.Offset(rayDirection * minHit.Distance);
|
||||
var hitPos = _transformSystem.ToCoordinates(hitMapCoord);
|
||||
// if we somehow hit something not directly parented to space or a grid
|
||||
if (hitXform.Coordinates.EntityId != hitXform.GridUid && hitXform.GridUid != null)
|
||||
hitPos = _transformSystem.WithEntityId(hitPos, hitXform.GridUid.Value);
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Content.Server.Shuttles.Components
|
||||
public const float BrakeCoefficient = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum velocity assuming TWR is BaseMaxVelocityTWR.
|
||||
/// Mono - velocity that 2x slowdown will be applied at. (4x at 2x this, 8x at 3x this, etc.)
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float BaseMaxLinearVelocity = 75f; // Mono
|
||||
public float BaseMaxLinearVelocity = 80f;
|
||||
|
||||
public const float MaxAngularVelocity = 4f;
|
||||
|
||||
@@ -76,35 +76,23 @@ namespace Content.Server.Shuttles.Components
|
||||
public float DampingModifier;
|
||||
|
||||
// <Mono>
|
||||
/// <summary>
|
||||
/// Limit to max velocity set by a shuttle console.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SetMaxVelocity = 140f;
|
||||
|
||||
/// <summary>
|
||||
/// At what Thrust-Weight-Ratio should this ship have the base max velocity as its maximum velocity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float BaseMaxVelocityTWR = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// How much should TWR affect max velocity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaxVelocityScalingExponent = 0.25f; // 16x thrust = 2x max speed
|
||||
|
||||
/// <summary>
|
||||
/// Don't allow max velocity to go beyond this value.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float UpperMaxVelocity = 140f; // we ball
|
||||
|
||||
/// <summary>
|
||||
/// Thrust acceleration vector last update.
|
||||
/// </summar>
|
||||
[DataField]
|
||||
public Vector2 LastThrust = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier to angular thrust. Set depending on pilot.
|
||||
/// </summary
|
||||
[ViewVariables]
|
||||
public float AngularMultiplier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier to linear thrust. Set depending on pilot.
|
||||
/// </summary
|
||||
[ViewVariables]
|
||||
public float AccelerationMultiplier = 1f;
|
||||
// </Mono>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,18 +457,20 @@ namespace Content.Server.Shuttles.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
var shuttleUid = Transform(console.Value).GridUid;
|
||||
|
||||
if (!CanShuttleDock(shuttleUid))
|
||||
if (!TryGetEntity(args.DockEntity, out var ourDock) ||
|
||||
!TryGetEntity(args.TargetDockEntity, out var targetDock) ||
|
||||
!TryComp(ourDock, out DockingComponent? ourDockComp) ||
|
||||
!TryComp(targetDock, out DockingComponent? targetDockComp))
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetEntity(args.DockEntity, out var ourDock) ||
|
||||
!TryGetEntity(args.TargetDockEntity, out var targetDock) ||
|
||||
!TryComp(ourDock, out DockingComponent? ourDockComp) ||
|
||||
!TryComp(targetDock, out DockingComponent? targetDockComp))
|
||||
var shuttleUid = Transform(console.Value).GridUid;
|
||||
var otherShuttleUid = Transform(targetDock.Value).GridUid; // Mono
|
||||
|
||||
// Mono - check both grids
|
||||
if (!CanShuttleDock(shuttleUid) || !CanShuttleDock(otherShuttleUid))
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("shuttle-console-dock-fail"));
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Content.Server.Shuttles.Systems;
|
||||
|
||||
public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!; // Mono
|
||||
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
@@ -39,14 +40,20 @@ public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
|
||||
protected override void UpdateState(EntityUid uid, RadarConsoleComponent component)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
var onGrid = xform.ParentUid == xform.GridUid;
|
||||
EntityCoordinates? coordinates = onGrid ? xform.Coordinates : null;
|
||||
Angle? angle = onGrid ? xform.LocalRotation : null;
|
||||
// Mono
|
||||
var parentUid = xform.GridUid;
|
||||
EntityCoordinates? coordinates = null;
|
||||
Angle? angle = null;
|
||||
if (component.FollowEntity)
|
||||
{
|
||||
coordinates = new EntityCoordinates(uid, Vector2.Zero);
|
||||
angle = Angle.Zero; // Frontier: Angle.Zero<Angle.FromDegrees(180) // Mono - frontier strikes again
|
||||
}
|
||||
else if (parentUid is { } parent)
|
||||
{
|
||||
coordinates = _transform.WithEntityId(xform.Coordinates, parent);
|
||||
angle = _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(parent);
|
||||
}
|
||||
|
||||
if (_uiSystem.HasUi(uid, RadarConsoleUiKey.Key))
|
||||
{
|
||||
@@ -62,8 +69,6 @@ public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
|
||||
state = _console.GetNavState(uid, docks);
|
||||
}
|
||||
|
||||
state.RotateWithEntity = !component.FollowEntity;
|
||||
|
||||
// Frontier: ghost radar restrictions
|
||||
if (component.MaxIffRange != null)
|
||||
state.MaxIffRange = component.MaxIffRange.Value;
|
||||
|
||||
@@ -202,7 +202,6 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
{
|
||||
DockingInterfaceState? dockState = null;
|
||||
UpdateState(uid, ref dockState);
|
||||
_shuttle.NfSetPowered(uid, component, args.Powered); // Frontier
|
||||
|
||||
// Handle job slots when power changes
|
||||
HandleJobSlotsOnPowerChange(uid, component, args.Powered);
|
||||
@@ -557,9 +556,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
angle,
|
||||
docks,
|
||||
_shuttle.NfGetInertiaDampeningMode(entity), // Frontier: inertia dampening
|
||||
portNames,
|
||||
entity.Comp1.Pannable, // Mono
|
||||
entity.Comp1.RelativePanning); // Mono
|
||||
portNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -225,16 +225,19 @@ public sealed partial class ShuttleSystem
|
||||
var inelasticVel = totalInertia / (ourMass + otherMass);
|
||||
|
||||
// Mono Edit - partial credit to https://github.com/Sector-Crescent/Hullrot/pull/692
|
||||
//ShipShieldedComp is removed when shields are broken, only reduces energy delivered when shields are active. ShipShieldsSystem ln 256.
|
||||
//ShipShieldedComp is removed when shields are broken, reduces both energies when shields are active. ShipShieldsSystem ln 256.
|
||||
float shieldFactor = 1f;
|
||||
if (TryComp<ShipShieldedComponent>(args.OurEntity, out var ShipShieldedComponent) //Our ship collision resistance
|
||||
&& TryComp<ShipShieldEmitterComponent>(ShipShieldedComponent.Source, out var ShipShieldEmitterComponent)
|
||||
)
|
||||
toUsEnergy *= ShipShieldEmitterComponent.CollisionResistanceMultiplier;
|
||||
shieldFactor *= ShipShieldEmitterComponent.CollisionResistanceMultiplier;
|
||||
|
||||
if (TryComp<ShipShieldedComponent>(args.OtherEntity, out var OtherShipShieldedComponent) //Other ship collision resistance
|
||||
&& TryComp<ShipShieldEmitterComponent>(OtherShipShieldedComponent.Source, out var OtherShipShieldEmitterComponent)
|
||||
)
|
||||
toOtherEnergy *= OtherShipShieldEmitterComponent.CollisionResistanceMultiplier;
|
||||
shieldFactor *= OtherShipShieldEmitterComponent.CollisionResistanceMultiplier;
|
||||
toUsEnergy *= shieldFactor;
|
||||
toOtherEnergy *= shieldFactor;
|
||||
// Mono Edit end
|
||||
|
||||
DoGridImpact((args.OurEntity, ourGrid, ourXform, ourBody), args.OurFixture, inelasticVel, ourVelocity, ourTile, ourTiles, toUsEnergy);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
@@ -28,6 +29,23 @@ public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
|
||||
if (TryComp(ent, out ActiveRadioComponent? activeRadio))
|
||||
activeRadio.Channels = [.. radioChannels];
|
||||
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
if (TryComp(ent, out AiRemoteControllerComponent? aiRemoteComp))
|
||||
{
|
||||
if (TryComp(aiRemoteComp.AiHolder, out IntrinsicRadioTransmitterComponent? stationAiTransmitter) && transmitter != null)
|
||||
{
|
||||
aiRemoteComp.PreviouslyTransmitterChannels = [.. radioChannels];
|
||||
transmitter.Channels = [.. stationAiTransmitter.Channels];
|
||||
}
|
||||
|
||||
if (TryComp(aiRemoteComp.AiHolder, out ActiveRadioComponent? stationAiActiveRadio) && activeRadio != null)
|
||||
{
|
||||
aiRemoteComp.PreviouslyActiveRadioChannels = [.. radioChannels];
|
||||
activeRadio.Channels = [.. stationAiActiveRadio.Channels];
|
||||
}
|
||||
}
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
|
||||
// Borg transponder for the robotics console
|
||||
if (TryComp(ent, out BorgTransponderComponent? transponder))
|
||||
{
|
||||
|
||||
@@ -6,7 +6,9 @@ using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components; // Corvax-Next-AiRemoteControl
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
@@ -47,7 +49,8 @@ public sealed partial class BorgSystem
|
||||
charge,
|
||||
chassis.ModuleCount,
|
||||
hasBrain,
|
||||
canDisable);
|
||||
canDisable,
|
||||
HasComp<AiRemoteControllerComponent>(uid)); // Corvax-Next-AiRemoteControl
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
@@ -25,6 +26,8 @@ using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Shared.StationAi;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Wires;
|
||||
@@ -144,6 +147,21 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
args.Handled = true;
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
if (component.BrainEntity == null && HasComp<AiRemoteBrainComponent>(used) &&
|
||||
_whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used))
|
||||
{
|
||||
EnsureComp<AiRemoteControllerComponent>(uid);
|
||||
_container.Insert(used, component.BrainContainer);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} installed ai remote brain {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
|
||||
args.Handled = true;
|
||||
BorgActivate(uid, component);
|
||||
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,6 +196,15 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
{
|
||||
_mind.TransferTo(mindId, args.Entity, mind: mind);
|
||||
}
|
||||
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
if (HasComp<AiRemoteBrainComponent>(args.Entity))
|
||||
{
|
||||
BorgDeactivate(uid, component);
|
||||
RemComp<AiRemoteControllerComponent>(uid);
|
||||
RemComp<StationAiVisionComponent>(uid);
|
||||
}
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
}
|
||||
|
||||
private void OnMindAdded(EntityUid uid, BorgChassisComponent component, MindAddedMessage args)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components; // Corvax-Next-AiRemoteControl
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Emag.Systems;
|
||||
@@ -13,6 +14,9 @@ using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Silicons.StationAi; // Corvax-Next-AiRemoteControl
|
||||
using Content.Shared.Tag; // Corvax-Next-AiRemoteControl
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -33,6 +37,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!; // Corvax-Next-AiRemoteControl
|
||||
[Dependency] private readonly IMapManager _map = default!; // Mono - Law update is grid-only now.
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -63,6 +68,12 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
if (HasComp<AiRemoteControllerComponent>(uid)
|
||||
|| _tagSystem.HasTag(uid, "StationAi")) // skip a law's notification for remotable and AI
|
||||
return;
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
|
||||
var msg = Loc.GetString("laws-notify");
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.FromHex("#5ed7aa"));
|
||||
@@ -155,6 +166,11 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
if (component.Lawset == null)
|
||||
component.Lawset = GetLawset(component.Laws);
|
||||
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
if (HasComp<AiRemoteControllerComponent>(uid)) // You can't emag controllable entities
|
||||
return;
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
|
||||
// Show the silicon has been subverted.
|
||||
component.Subverted = true;
|
||||
|
||||
@@ -281,7 +297,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
/// <summary>
|
||||
/// Set the laws of a silicon entity while notifying the player.
|
||||
/// </summary>
|
||||
public void SetLaws(List<SiliconLaw> newLaws, EntityUid target, SoundSpecifier? cue = null)
|
||||
public void SetLaws(List<SiliconLaw> newLaws, EntityUid target, SoundSpecifier? cue = null, bool silent = false) // AiRemoteControl - silent
|
||||
{
|
||||
if (!TryComp<SiliconLawProviderComponent>(target, out var component))
|
||||
return;
|
||||
@@ -290,7 +306,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
component.Lawset = new SiliconLawset();
|
||||
|
||||
component.Lawset.Laws = newLaws;
|
||||
NotifyLawsChanged(target, cue);
|
||||
if (!silent) // AiRemoteControl - silent
|
||||
NotifyLawsChanged(target, cue);
|
||||
}
|
||||
|
||||
protected override void OnUpdaterInsert(Entity<SiliconLawUpdaterComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
@@ -312,8 +329,16 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
continue;
|
||||
|
||||
SetLaws(lawset, update, provider.LawUploadSound);
|
||||
// Mono edit end
|
||||
// Corvax-Next-AiRemoteControl-Start
|
||||
if (TryComp<StationAiHeldComponent>(update, out var stationAiHeldComp)
|
||||
&& stationAiHeldComp.CurrentConnectedEntity != null
|
||||
&& HasComp<SiliconLawProviderComponent>(stationAiHeldComp.CurrentConnectedEntity))
|
||||
{
|
||||
SetLaws(lawset, stationAiHeldComp.CurrentConnectedEntity.Value, provider.LawUploadSound);
|
||||
}
|
||||
// Corvax-Next-AiRemoteControl-End
|
||||
}
|
||||
// Mono edit end
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -346,11 +346,11 @@ namespace Content.Server.VendingMachines
|
||||
if (TryComp<MarketModifierComponent>(component.Owner, out var modifier))
|
||||
price *= modifier.Mod;
|
||||
|
||||
var totalPrice = (int) price;
|
||||
var totalPrice = component.RequiresCash ? (int) price : 0;
|
||||
|
||||
// If any price has a vendor price, explicitly use its value - higher OR lower, over others.
|
||||
var priceVend = _pricing.GetEstimatedVendPrice(proto);
|
||||
if (priceVend > 0.0) // if vending price exists, overwrite it.
|
||||
if (priceVend > 0.0 && component.RequiresCash) // if vending price exists, overwrite it.
|
||||
totalPrice = (int) priceVend;
|
||||
|
||||
if (IsAuthorized(uid, sender, component))
|
||||
|
||||
@@ -91,20 +91,19 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
}
|
||||
}
|
||||
|
||||
var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
|
||||
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
|
||||
var fromMap = TransformSystem.ToMapCoordinates(fromCoordinates);
|
||||
var toMap = TransformSystem.ToMapCoordinates(toCoordinates).Position;
|
||||
var mapDirection = toMap - fromMap.Position;
|
||||
var mapAngle = mapDirection.ToAngle();
|
||||
|
||||
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
|
||||
var fromEnt = MapManager.TryFindGridAt(fromMap, out var gridUid, out var grid)
|
||||
? fromCoordinates.WithEntityId(gridUid, EntityManager)
|
||||
: new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
|
||||
? _transform.WithEntityId(fromCoordinates, gridUid)
|
||||
: _transform.ToCoordinates(fromMap);
|
||||
|
||||
// get gun's local velocity
|
||||
// get gun's fromEnt-relative velocity
|
||||
var gunVelocity = Vector2.Zero;
|
||||
if (_physQuery.TryComp(gunUid, out var gunBody))
|
||||
gunVelocity = gunBody.LinearVelocity;
|
||||
gunVelocity = Physics.GetMapLinearVelocity(gunUid) - Physics.GetMapLinearVelocity(fromEnt);
|
||||
|
||||
// I must be high because this was getting tripped even when true.
|
||||
// DebugTools.Assert(direction != Vector2.Zero);
|
||||
@@ -165,7 +164,8 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
CreateAndFireProjectiles(ent.Value, newAmmo, offset);
|
||||
|
||||
break;
|
||||
case HitscanAmmoComponent:
|
||||
|
||||
case HitscanAmmoComponent hitscanammo:
|
||||
if (ent == null)
|
||||
break;
|
||||
|
||||
@@ -179,10 +179,13 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
};
|
||||
RaiseLocalEvent(ent.Value, ref hitscanEv);
|
||||
|
||||
Del(ent);
|
||||
|
||||
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
||||
// Mono start
|
||||
Spawn(hitscanammo.CasingPrototype, fromEnt);
|
||||
Del(ent);
|
||||
// Mono end
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@@ -235,7 +238,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
}
|
||||
|
||||
// Do a throw
|
||||
if (!HasComp<ProjectileComponent>(uid))
|
||||
if (!TryComp(uid, out ProjectileComponent? projectileComp))
|
||||
{
|
||||
RemoveShootable(uid);
|
||||
// TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack.
|
||||
@@ -251,6 +254,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
predicted.ClientEnt = user;
|
||||
}
|
||||
|
||||
projectileComp.Damage *= gun.DamageModifier;
|
||||
ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeedModified, offset); // Mono - add offset
|
||||
if (HasComp<FireControllableComponent>(gunUid))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 ImHoks <142083149+ImHoks@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 ImHoks <imhokzzzz@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 KillanGenifer <killangenifer@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs;
|
||||
using Content.Shared._CorvaxNext.Silicons.Borgs.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Shared.StationAi;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Map; // Mono
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
|
||||
namespace Content.Server._CorvaxNext.Silicons.Borgs;
|
||||
|
||||
public sealed class AiRemoteControlSystem : SharedAiRemoteControlSystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SiliconLawSystem _lawSystem = default!;
|
||||
[Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
[Dependency] private readonly IMapManager _map = default!; // Mono
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AiRemoteControllerComponent, ReturnMindIntoAiEvent>(OnReturnMindIntoAi);
|
||||
SubscribeLocalEvent<AiRemoteControllerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<AiRemoteControllerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<AiRemoteControllerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<StationAiHeldComponent, AiRemoteControllerComponent.RemoteDeviceActionMessage>(OnUiRemoteAction);
|
||||
SubscribeLocalEvent<StationAiHeldComponent, ToggleRemoteDevicesScreenEvent>(OnToggleRemoteDevicesScreen);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<AiRemoteControllerComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
var visionComp = EnsureComp<StationAiVisionComponent>(entity.Owner);
|
||||
EntityUid? actionEnt = null;
|
||||
|
||||
_actions.AddAction(entity.Owner, ref actionEnt, entity.Comp.BackToAiAction);
|
||||
|
||||
if (actionEnt != null)
|
||||
entity.Comp.BackToAiActionEntity = actionEnt.Value;
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<AiRemoteControllerComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
_actions.RemoveAction(entity.Owner, entity.Comp.BackToAiActionEntity);
|
||||
|
||||
var backArgs = new ReturnMindIntoAiEvent();
|
||||
backArgs.Performer = entity;
|
||||
|
||||
if (TryComp(entity, out IntrinsicRadioTransmitterComponent? transmitter)
|
||||
&& entity.Comp.PreviouslyTransmitterChannels != null)
|
||||
transmitter.Channels = [.. entity.Comp.PreviouslyTransmitterChannels];
|
||||
|
||||
if (TryComp(entity, out ActiveRadioComponent? activeRadio)
|
||||
&& entity.Comp.PreviouslyActiveRadioChannels != null)
|
||||
activeRadio.Channels = [.. entity.Comp.PreviouslyActiveRadioChannels];
|
||||
|
||||
ReturnMindIntoAi(entity);
|
||||
}
|
||||
|
||||
private void OnGetVerbs(Entity<AiRemoteControllerComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
var user = args.User;
|
||||
|
||||
if (!TryComp<StationAiHeldComponent>(user, out var stationAiHeldComp))
|
||||
return;
|
||||
|
||||
var verb = new AlternativeVerb
|
||||
{
|
||||
Text = Loc.GetString("ai-remote-control"),
|
||||
Act = () => AiTakeControl(user, entity)
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnReturnMindIntoAi(Entity<AiRemoteControllerComponent> entity, ref ReturnMindIntoAiEvent args) =>
|
||||
ReturnMindIntoAi(entity);
|
||||
|
||||
public void AiTakeControl(EntityUid ai, EntityUid entity)
|
||||
{
|
||||
if (!_mind.TryGetMind(ai, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
if (_mind.TryGetMind(entity, out _, out _))
|
||||
return;
|
||||
|
||||
if (!TryComp<StationAiHeldComponent>(ai, out var stationAiHeldComp))
|
||||
return;
|
||||
|
||||
if (!TryComp<AiRemoteControllerComponent>(entity, out var aiRemoteComp))
|
||||
return;
|
||||
|
||||
if (!_map.TryFindGridAt(Transform(ai).MapPosition, out var grid, out var _) || Transform(entity).GridUid != grid)
|
||||
return; // Mono no controlling borgs outside the ai's grid.
|
||||
|
||||
if (TryComp(entity, out IntrinsicRadioTransmitterComponent? transmitter))
|
||||
{
|
||||
aiRemoteComp.PreviouslyTransmitterChannels = [.. transmitter.Channels];
|
||||
|
||||
if (TryComp(ai, out IntrinsicRadioTransmitterComponent? stationAiTransmitter))
|
||||
transmitter.Channels = [.. stationAiTransmitter.Channels];
|
||||
}
|
||||
|
||||
if (TryComp(entity, out ActiveRadioComponent? activeRadio))
|
||||
{
|
||||
aiRemoteComp.PreviouslyActiveRadioChannels = [.. activeRadio.Channels];
|
||||
|
||||
if (TryComp(ai, out ActiveRadioComponent? stationAiActiveRadio))
|
||||
activeRadio.Channels = [.. stationAiActiveRadio.Channels];
|
||||
}
|
||||
|
||||
_mind.ControlMob(ai, entity);
|
||||
aiRemoteComp.AiHolder = ai;
|
||||
aiRemoteComp.LinkedMind = mindId;
|
||||
|
||||
stationAiHeldComp.CurrentConnectedEntity = entity;
|
||||
|
||||
if (!_stationAiSystem.TryGetCore(ai, out var stationAiCore))
|
||||
return;
|
||||
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, false);
|
||||
|
||||
RewriteLaws(ai, entity);
|
||||
}
|
||||
|
||||
private void OnToggleRemoteDevicesScreen(EntityUid uid, StationAiHeldComponent component, ToggleRemoteDevicesScreenEvent args)
|
||||
{
|
||||
if (args.Handled || !TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
_userInterface.TryToggleUi(uid, RemoteDeviceUiKey.Key, actor.PlayerSession);
|
||||
|
||||
var aiGrid = Transform(uid).GridUid; // Mono
|
||||
var query = EntityManager.EntityQueryEnumerator<AiRemoteControllerComponent>();
|
||||
var remoteDevices = new List<RemoteDevicesData>();
|
||||
|
||||
while (query.MoveNext(out var queryUid, out var comp))
|
||||
{
|
||||
if (Transform(queryUid).GridUid != aiGrid)
|
||||
continue;
|
||||
var data = new RemoteDevicesData
|
||||
{
|
||||
NetEntityUid = GetNetEntity(queryUid),
|
||||
DisplayName = Comp<MetaDataComponent>(queryUid).EntityName
|
||||
};
|
||||
|
||||
remoteDevices.Add(data);
|
||||
}
|
||||
|
||||
var state = new RemoteDevicesBuiState(remoteDevices);
|
||||
_userInterface.SetUiState(uid, RemoteDeviceUiKey.Key, state);
|
||||
}
|
||||
|
||||
private void OnUiRemoteAction(EntityUid uid, StationAiHeldComponent component, AiRemoteControllerComponent.RemoteDeviceActionMessage msg)
|
||||
{
|
||||
if (msg.RemoteAction == null)
|
||||
return;
|
||||
|
||||
var target = GetEntity(msg.RemoteAction?.Target);
|
||||
|
||||
if (!HasComp<AiRemoteControllerComponent>(target))
|
||||
return;
|
||||
|
||||
switch (msg.RemoteAction?.ActionType)
|
||||
{
|
||||
case RemoteDeviceActionEvent.RemoteDeviceActionType.MoveToDevice:
|
||||
if (!_stationAiSystem.TryGetCore(uid, out var stationAiCore)
|
||||
|| stationAiCore.Comp?.RemoteEntity == null)
|
||||
return;
|
||||
_xformSystem.SetCoordinates(stationAiCore.Comp.RemoteEntity.Value, Transform(target.Value).Coordinates);
|
||||
break;
|
||||
|
||||
case RemoteDeviceActionEvent.RemoteDeviceActionType.TakeControl:
|
||||
AiTakeControl(uid, target.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RewriteLaws(EntityUid from, EntityUid to)
|
||||
{
|
||||
if (!TryComp<SiliconLawProviderComponent>(from, out var fromLawsComp))
|
||||
return;
|
||||
|
||||
if (!TryComp<SiliconLawProviderComponent>(to, out var toLawsComp))
|
||||
return;
|
||||
|
||||
if (fromLawsComp.Lawset == null)
|
||||
return;
|
||||
|
||||
var fromLaws = _lawSystem.GetLaws(from);
|
||||
_lawSystem.SetLaws(fromLaws.Laws, to, silent: true);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ public sealed class JobWhitelistsCommand : LocalizedCommands
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-ban-invalid-arguments"));
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Server.Atmos.Rotting;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Atmos.Rotting;
|
||||
@@ -9,9 +11,11 @@ using Content.Shared.DoAfter;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
using Content.Shared.Medical.CPR;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -32,6 +36,8 @@ public sealed class CPRSystem : EntitySystem
|
||||
[Dependency] private readonly RottingSystem _rottingSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!; // Mono
|
||||
[Dependency] private readonly EuiManager _euiManager = default!; // Mono
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -103,26 +109,41 @@ public sealed class CPRSystem : EntitySystem
|
||||
|
||||
private void OnCPRDoAfter(Entity<CPRTrainingComponent> performer, ref CPRDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Handled || !args.Target.HasValue)
|
||||
if (args.Cancelled || args.Handled || args.Target is not { } target) // Mono: replaces args.Target with target, makes target not nullable
|
||||
{
|
||||
performer.Comp.CPRPlayingStream = _audio.Stop(performer.Comp.CPRPlayingStream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!performer.Comp.CPRHealing.Empty)
|
||||
_damageable.TryChangeDamage(args.Target, performer.Comp.CPRHealing, true, origin: performer);
|
||||
_damageable.TryChangeDamage(target, performer.Comp.CPRHealing, true, origin: performer);
|
||||
|
||||
if (performer.Comp.RotReductionMultiplier > 0)
|
||||
_rottingSystem.ReduceAccumulator(
|
||||
(EntityUid)args.Target, performer.Comp.DoAfterDuration * performer.Comp.RotReductionMultiplier);
|
||||
|
||||
if (_robustRandom.Prob(performer.Comp.ResuscitationChance)
|
||||
&& _mobThreshold.TryGetThresholdForState((EntityUid)args.Target, MobState.Dead, out var threshold)
|
||||
&& TryComp<DamageableComponent>(args.Target, out var damageableComponent)
|
||||
&& TryComp<MobStateComponent>(args.Target, out var state)
|
||||
&& _mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold)
|
||||
&& TryComp<DamageableComponent>(target, out var damageableComponent)
|
||||
&& !HasComp<UnrevivableComponent>(target) // Mono: Checks unreviveability
|
||||
&& TryComp<MobStateComponent>(target, out var state)
|
||||
&& damageableComponent.TotalDamage < threshold)
|
||||
{
|
||||
_mobStateSystem.ChangeMobState(args.Target.Value, MobState.Critical, state, performer);
|
||||
|
||||
// Mono Edit: Informs the ghost they've been revived.
|
||||
if (_mind.TryGetMind(target, out _, out var mind) &&
|
||||
mind.Session is { } playerSession)
|
||||
{
|
||||
// notify them they're being revived.
|
||||
if (mind.CurrentEntity != target)
|
||||
{
|
||||
_euiManager.OpenEui(new ReturnToBodyEui(mind, _mind), playerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var isAlive = _mobStateSystem.IsAlive(args.Target.Value);
|
||||
args.Repeat = !isAlive;
|
||||
if (isAlive)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Linq;
|
||||
using Content.Server._Mono.Grid;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared._Mono.Grid;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Mono.Administration.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command that allows you to apply grid modifiers to existing grids
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class AddGridModifier : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public string Command => "addgridmodifier";
|
||||
public string Description => "Applies grid modification to chosen grid.";
|
||||
public string Help => $"Usage: {Command} <gridUid> <modification...>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
|
||||
if (args.Length < 2)
|
||||
shell.WriteLine($"Not enough arguments.\n{Help}");
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var uidNet) || !_entManager.TryGetEntity(uidNet, out var uid))
|
||||
{
|
||||
shell.WriteLine($"Invalid entity id.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.TryGetComponent(uid, out MapGridComponent? map))
|
||||
{
|
||||
shell.WriteLine($"Entity is not a grid.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<ProtoId<GridModificationPrototype>> modifiers = [];
|
||||
var gridMod = _entManager.System<GridModifierSystem>();
|
||||
|
||||
foreach (var mod in args.Skip(1))
|
||||
{
|
||||
modifiers.Add(mod);
|
||||
}
|
||||
|
||||
gridMod.ModifyGrid(uid.Value, modifiers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared._Mono.Company;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
using Content.Server.Administration;
|
||||
|
||||
namespace Content.Server._Mono.Company;
|
||||
|
||||
[ToolshedCommand(Name = "company"), AnyCommand]
|
||||
public sealed class CompanyCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly CompanyManager _company = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
[Dependency] private readonly IAdminManager _admin = default!;
|
||||
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||
|
||||
[CommandImplementation("addmember")]
|
||||
public async void Add(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] string username,
|
||||
[CommandArgument] ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_prototypes.TryIndex(company, out var companyPrototype))
|
||||
{
|
||||
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.Session == null
|
||||
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
|
||||
&& !_company.IsOwner(ctx.Session, company)) // owner can add members
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
var lookup = await _playerLocator.LookupIdByNameAsync(username);
|
||||
if (lookup == null)
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_company.IsMember(lookup.UserId, company))
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-memberadd-already-whitelisted",
|
||||
("player", username),
|
||||
("companyId", company.Id),
|
||||
("companyName", companyPrototype.Name)));
|
||||
return;
|
||||
}
|
||||
|
||||
_company.AddMember(lookup.UserId, company);
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-memberadd-added",
|
||||
("player", username),
|
||||
("companyId", company.Id),
|
||||
("companyName", companyPrototype.Name)));
|
||||
}
|
||||
|
||||
[CommandImplementation("playercompanies")]
|
||||
public async void GetPlayerWhitelist(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] string username)
|
||||
{
|
||||
if (ctx.Session == null
|
||||
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
|
||||
&& ctx.Session.Name != username) // allow looking at own companies
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
var lookup = await _playerLocator.LookupIdByNameAsync(username);
|
||||
if (lookup == null)
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
|
||||
return;
|
||||
}
|
||||
|
||||
var whitelists = _company.GetPlayerCompanies(lookup.UserId);
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-playercompanies-whitelisted-for",
|
||||
("player", username),
|
||||
("companies", string.Join(", ", whitelists))));
|
||||
}
|
||||
|
||||
[CommandImplementation("members")]
|
||||
public async void GetCompanyWhitelist(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_prototypes.TryIndex(company, out var companyPrototype))
|
||||
{
|
||||
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.Session == null
|
||||
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
|
||||
&& !_company.IsMember(ctx.Session.UserId, company)) // members can see other members
|
||||
{
|
||||
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
var whitelisted = _company.GetCompanyMembers(company);
|
||||
|
||||
var members = whitelisted.Where(w => !w.Owner).Select(m => m.LastSeenUserName);
|
||||
var owners = whitelisted.Where(w => w.Owner).Select(m => m.LastSeenUserName);
|
||||
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-members-whitelisted-for",
|
||||
("company", company),
|
||||
("players", string.Join(", ", members)),
|
||||
("owners", string.Join(", ", owners))));
|
||||
}
|
||||
|
||||
[CommandImplementation("setowner")]
|
||||
public async void SetOwner(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] ProtoId<CompanyPrototype> company,
|
||||
[CommandArgument] string username,
|
||||
[CommandArgument] bool owner
|
||||
)
|
||||
{
|
||||
if (!_prototypes.TryIndex(company, out var companyPrototype))
|
||||
{
|
||||
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
|
||||
return;
|
||||
}
|
||||
|
||||
// only admins can set owner
|
||||
if (ctx.Session == null
|
||||
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist))
|
||||
{
|
||||
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we're trying to owner a non-member
|
||||
var lookup = await _playerLocator.LookupIdByNameAsync(username);
|
||||
if (lookup == null)
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_company.IsMember(lookup.UserId, company))
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-was-not-whitelisted",
|
||||
("player", username),
|
||||
("companyId", company.Id),
|
||||
("companyName", companyPrototype.Name)));
|
||||
return;
|
||||
}
|
||||
|
||||
_company.SetOwner(company, lookup.UserId, owner);
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-setowner-success", ("player", username), ("status", owner)));
|
||||
}
|
||||
|
||||
[CommandImplementation("removemember")]
|
||||
public async void Remove(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] string username,
|
||||
[CommandArgument] ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_prototypes.TryIndex(company, out var companyPrototype))
|
||||
{
|
||||
ctx.ReportError(new NotAValidPrototype(company, nameof(CompanyPrototype)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.Session == null
|
||||
|| !_admin.HasAdminFlag(ctx.Session, AdminFlags.Whitelist)
|
||||
&& !_company.IsOwner(ctx.Session, company)) // owner can remove members
|
||||
{
|
||||
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-not-enough-permissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
var lookup = await _playerLocator.LookupIdByNameAsync(username);
|
||||
if (lookup == null)
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-player-not-found", ("player", username)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_company.IsMember(lookup.UserId, company))
|
||||
{
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-was-not-whitelisted",
|
||||
("player", username),
|
||||
("companyId", company.Id),
|
||||
("companyName", companyPrototype.Name)));
|
||||
return;
|
||||
}
|
||||
|
||||
await _company.RemoveMember(lookup.UserId, company);
|
||||
ctx.WriteLine(Loc.GetString("cmd-company-memberremove-removed",
|
||||
("player", username),
|
||||
("companyId", company.Id),
|
||||
("companyName", companyPrototype.Name)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared._Mono.CCVar;
|
||||
using Content.Shared._Mono.Company;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._Mono.Company;
|
||||
|
||||
public sealed class CompanyManager
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private readonly Dictionary<ProtoId<CompanyPrototype>, HashSet<CompanyMemberRecord>> _companies = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _log.GetSawmill("company.manager");
|
||||
|
||||
_net.RegisterNetMessage<MsgCompanyWhitelist>();
|
||||
_net.Connected += OnConnected;
|
||||
|
||||
_ = LoadCompaniesData();
|
||||
}
|
||||
|
||||
private async Task LoadCompaniesData()
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
var members = await _db.GetAllCompanyMembers();
|
||||
foreach (var proto in _proto.EnumeratePrototypes<CompanyPrototype>())
|
||||
{
|
||||
_companies.Add(proto.ID, new());
|
||||
_companies[proto.ID].UnionWith(members.Where(m => m.Company == proto.ID).ToHashSet());
|
||||
}
|
||||
|
||||
_sawmill.Info($"All company members data loaded in {sw.Elapsed.TotalSeconds:.2}s");
|
||||
}
|
||||
|
||||
private void OnConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
SendCompanyWhitelist(e.Channel);
|
||||
}
|
||||
|
||||
private async Task<HashSet<CompanyMemberRecord>> QueryCompanyMembers(ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
return (await _db.GetCompanyMembers(company)).ToHashSet();
|
||||
}
|
||||
|
||||
public HashSet<CompanyMemberRecord> GetCompanyMembers(ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_companies.TryGetValue(company, out var members))
|
||||
return new();
|
||||
return members;
|
||||
}
|
||||
|
||||
public HashSet<CompanyMemberRecord> GetAllCompanyMembers()
|
||||
{
|
||||
return _companies.Values.SelectMany(v => v).ToHashSet();
|
||||
}
|
||||
|
||||
public CompanyMemberRecord? GetCompanyMember(ProtoId<CompanyPrototype> company, NetUserId player)
|
||||
{
|
||||
if (!_companies.TryGetValue(company, out var members))
|
||||
return null;
|
||||
|
||||
return members.FirstOrNull(m => m.PlayerUserId == player);
|
||||
}
|
||||
|
||||
public async void AddMember(NetUserId player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
await _db.AddCompanyMember(player, company);
|
||||
|
||||
var member = await _db.GetCompanyMember(company, player);
|
||||
if (member != null)
|
||||
_companies[company].Add(member.Value);
|
||||
|
||||
if (_player.TryGetSessionById(player, out var session))
|
||||
SendCompanyWhitelist(session.Channel);
|
||||
}
|
||||
|
||||
public bool IsAllowed(ICommonSession session, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_config.GetCVar(MonoCVars.CompanyWhitelist))
|
||||
return true;
|
||||
|
||||
if (!_proto.TryIndex(company, out var companyPrototype) ||
|
||||
!companyPrototype.Whitelisted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsMember(session.UserId, company);
|
||||
}
|
||||
|
||||
public bool IsOwner(ICommonSession session, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
if (!_companies.TryGetValue(company, out var members))
|
||||
return false;
|
||||
|
||||
var member = members.FirstOrNull(m => m.PlayerUserId == session.UserId);
|
||||
|
||||
return member != null && member.Value.Owner;
|
||||
}
|
||||
|
||||
public bool SetOwner(ProtoId<CompanyPrototype> company, NetUserId userId, bool owner)
|
||||
{
|
||||
var cached = GetCompanyMember(company, userId);
|
||||
|
||||
if (cached is not { } member)
|
||||
return false;
|
||||
|
||||
if (owner == member.Owner)
|
||||
return true;
|
||||
|
||||
_db.SetCompanyOwner(company, userId, owner);
|
||||
|
||||
_companies[company].RemoveWhere(w => w.PlayerUserId == userId);
|
||||
member.Owner = owner; // company member is struct so we got a copy here
|
||||
_companies[company].Add(member);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsMember(NetUserId player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
var member = GetCompanyMember(company, player);
|
||||
return member != null;
|
||||
}
|
||||
|
||||
public async Task RemoveMember(NetUserId player, ProtoId<CompanyPrototype> company)
|
||||
{
|
||||
_companies[company].RemoveWhere(w => w.PlayerUserId == player);
|
||||
|
||||
await _db.RemoveCompanyMember(player, company);
|
||||
|
||||
if (_player.TryGetSessionById(player, out var session))
|
||||
SendCompanyWhitelist(session.Channel);
|
||||
}
|
||||
|
||||
public HashSet<ProtoId<CompanyPrototype>> GetPlayerCompanies(NetUserId player)
|
||||
{
|
||||
var res = new HashSet<ProtoId<CompanyPrototype>>();
|
||||
|
||||
foreach (var (key, members) in _companies)
|
||||
{
|
||||
var member = members.FirstOrNull(m => m.PlayerUserId == player);
|
||||
if (member != null)
|
||||
res.Add(key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public void SendCompanyWhitelist(INetChannel player)
|
||||
{
|
||||
var msg = new MsgCompanyWhitelist
|
||||
{
|
||||
Whitelist = GetPlayerCompanies(player.UserId)
|
||||
};
|
||||
|
||||
_net.ServerSendMessage(msg, player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Content.Shared._Mono.Company;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Mono.Company;
|
||||
|
||||
public struct CompanyMemberRecord
|
||||
{
|
||||
public Guid PlayerUserId;
|
||||
public string LastSeenUserName;
|
||||
public bool Owner;
|
||||
public ProtoId<CompanyPrototype> Company;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Content.Shared.GameTicking;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -19,10 +18,9 @@ namespace Content.Server._Mono.Company;
|
||||
public sealed class CompanySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedJobSystem _job = default!;
|
||||
[Dependency] private readonly SharedIdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
|
||||
[Dependency] private readonly CompanyManager _manager = default!;
|
||||
|
||||
// Dictionary to store original company preferences for players
|
||||
private readonly Dictionary<string, string> _playerOriginalCompanies = new();
|
||||
@@ -74,10 +72,9 @@ public sealed class CompanySystem : EntitySystem
|
||||
// or if their preference is "None"
|
||||
if (string.IsNullOrEmpty(profileCompany))
|
||||
{
|
||||
// Check for company login whitelists
|
||||
foreach (var companyProto in _prototypeManager.EnumeratePrototypes<CompanyPrototype>())
|
||||
{
|
||||
if (companyProto.Logins.Contains(args.Player.Name))
|
||||
if (_manager.IsAllowed(args.Player, companyProto))
|
||||
{
|
||||
companyComp.CompanyName = companyProto.ID;
|
||||
loginFound = true;
|
||||
|
||||
@@ -9,7 +9,6 @@ using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System;
|
||||
|
||||
namespace Content.Server._Mono.Detection;
|
||||
|
||||
@@ -21,18 +20,24 @@ public sealed class ThermalSignatureSystem : EntitySystem
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private float _updateInterval = 0.5f;
|
||||
private float _updateAccumulator = 0f;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private const float UpdateIntervalSeconds = 1f;
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(UpdateIntervalSeconds);
|
||||
private TimeSpan _nextUpdateTime;
|
||||
|
||||
private const float HeatChangeThreshold = 1.02f;
|
||||
|
||||
private List<Entity<ThermalSignatureComponent>> _gridQueue = new();
|
||||
|
||||
private EntityQuery<ThermalSignatureComponent> _sigQuery;
|
||||
private EntityQuery<GunComponent> _gunQuery;
|
||||
|
||||
private Dictionary<EntityUid, ThermalSignatureComponent> _gridCompMap = new();
|
||||
private EntityQuery<MapGridComponent> _mapGridQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialized);
|
||||
|
||||
// some of this could also be handled in shared but there's no point since PVS is a thing
|
||||
SubscribeLocalEvent<MachineThermalSignatureComponent, GetThermalSignatureEvent>(OnMachineGetSignature);
|
||||
SubscribeLocalEvent<PassiveThermalSignatureComponent, GetThermalSignatureEvent>(OnPassiveGetSignature);
|
||||
@@ -42,9 +47,14 @@ public sealed class ThermalSignatureSystem : EntitySystem
|
||||
SubscribeLocalEvent<ThrusterComponent, GetThermalSignatureEvent>(OnThrusterGetSignature);
|
||||
SubscribeLocalEvent<FTLDriveComponent, GetThermalSignatureEvent>(OnFTLGetSignature);
|
||||
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_sigQuery = GetEntityQuery<ThermalSignatureComponent>();
|
||||
_gunQuery = GetEntityQuery<GunComponent>();
|
||||
_mapGridQuery = GetEntityQuery<MapGridComponent>();
|
||||
}
|
||||
|
||||
private void OnGridInitialized(GridInitializeEvent args)
|
||||
{
|
||||
EnsureComp<ThermalSignatureComponent>(args.EntityUid);
|
||||
}
|
||||
|
||||
private void OnGunShot(Entity<ThermalSignatureComponent> ent, ref GunShotEvent args)
|
||||
@@ -87,48 +97,51 @@ public sealed class ThermalSignatureSystem : EntitySystem
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_updateAccumulator += frameTime;
|
||||
if (_updateAccumulator < _updateInterval)
|
||||
if (_timing.CurTime < _nextUpdateTime)
|
||||
return;
|
||||
_updateAccumulator -= _updateInterval;
|
||||
|
||||
var interval = _updateInterval;
|
||||
_nextUpdateTime = _timing.CurTime + UpdateInterval;
|
||||
|
||||
_gridCompMap.Clear();
|
||||
|
||||
var gridQuery = EntityQueryEnumerator<MapGridComponent>();
|
||||
while (gridQuery.MoveNext(out var uid, out _))
|
||||
var gridQuery = EntityQueryEnumerator<MapGridComponent, ThermalSignatureComponent>();
|
||||
while (gridQuery.MoveNext(out _, out _, out var gridSigComp))
|
||||
{
|
||||
if (!_sigQuery.TryComp(uid, out var sigComp))
|
||||
sigComp = EnsureComp<ThermalSignatureComponent>(uid);
|
||||
|
||||
sigComp.TotalHeat = 0f;
|
||||
_gridCompMap.Add(uid, sigComp);
|
||||
gridSigComp.TotalHeat = 0f;
|
||||
}
|
||||
|
||||
_gridQueue.Clear();
|
||||
var query = EntityQueryEnumerator<ThermalSignatureComponent>();
|
||||
while (query.MoveNext(out var uid, out var sigComp))
|
||||
{
|
||||
var ev = new GetThermalSignatureEvent(interval);
|
||||
var ev = new GetThermalSignatureEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
sigComp.StoredHeat += ev.Signature * interval;
|
||||
sigComp.StoredHeat *= MathF.Pow(sigComp.HeatDissipation, interval);
|
||||
if (_gridCompMap.ContainsKey(uid))
|
||||
|
||||
sigComp.StoredHeat += ev.Signature * UpdateIntervalSeconds;
|
||||
sigComp.StoredHeat *= MathF.Pow(sigComp.HeatDissipation, UpdateIntervalSeconds);
|
||||
|
||||
if (_mapGridQuery.HasComp(uid))
|
||||
{
|
||||
sigComp.TotalHeat += sigComp.StoredHeat;
|
||||
_gridQueue.Add((uid, sigComp));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
sigComp.TotalHeat = sigComp.StoredHeat;
|
||||
if (xform.GridUid != null && _gridCompMap.TryGetValue(xform.GridUid.Value, out var gridSig))
|
||||
if (xform.GridUid != null && _sigQuery.TryGetComponent(xform.GridUid.Value, out var gridSig))
|
||||
gridSig.TotalHeat += sigComp.StoredHeat;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (uid, sigComp) in _gridCompMap)
|
||||
{
|
||||
Dirty(uid, sigComp); // sync to client
|
||||
foreach (var ent in _gridQueue) {
|
||||
ent.Comp.TotalHeat += ent.Comp.StoredHeat;
|
||||
|
||||
// don't sync it if it didn't change heat much since last time, we don't need to sync 500 cold asteroids every system update
|
||||
if (ent.Comp.TotalHeat <= ent.Comp.LastUpdateHeat * HeatChangeThreshold
|
||||
&& ent.Comp.TotalHeat >= ent.Comp.LastUpdateHeat / HeatChangeThreshold)
|
||||
continue;
|
||||
|
||||
ent.Comp.LastUpdateHeat = ent.Comp.TotalHeat;
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Content.Shared._Mono.Grid;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Mono.Grid;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class GridModifierComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public List<ProtoId<GridModificationPrototype>> Modifications = [];
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Content.Shared._Mono.Grid;
|
||||
using Content.Shared._Mono.ShipRepair;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._Mono.Grid;
|
||||
|
||||
/// <summary>
|
||||
/// This handles grid modification on initialization.
|
||||
/// </summary>
|
||||
public sealed class GridModifierSystem : SharedGridModifierSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly SharedShipRepairSystem _repair = default!;
|
||||
|
||||
private List<EntityUid> _snapQueue = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GridModifierComponent, MapInitEvent>(OnInit);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var uid in _snapQueue)
|
||||
{
|
||||
_repair.GenerateRepairData(uid);
|
||||
}
|
||||
_snapQueue.Clear();
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, GridModifierComponent component, MapInitEvent args)
|
||||
{
|
||||
ModifyGrid(uid, component.Modifications);
|
||||
}
|
||||
|
||||
public void ModifyGrid(EntityUid uid, List<ProtoId<GridModificationPrototype>> modifiers)
|
||||
{
|
||||
if (!HasComp<MapGridComponent>(uid))
|
||||
return;
|
||||
|
||||
foreach (var modProto in modifiers)
|
||||
{
|
||||
if (!_protoMan.TryIndex(modProto, out var mod))
|
||||
continue;
|
||||
|
||||
foreach (var modifier in mod.Modifiers)
|
||||
{
|
||||
modifier.Modify(uid, EntityManager, _factory);
|
||||
}
|
||||
}
|
||||
|
||||
_snapQueue.Add(uid);
|
||||
}
|
||||
}
|
||||
@@ -57,12 +57,6 @@ public sealed partial class ShipFireGunsOperator : HTNOperator, IHtnConditionalS
|
||||
[DataField]
|
||||
public bool RequirePowered = true;
|
||||
|
||||
/// <summary>
|
||||
/// Stop targeting if beyond this range.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaxTargetingRange = 2000f;
|
||||
|
||||
private const string TargetingCancelToken = "ShipTargetingCancelToken";
|
||||
|
||||
public override void Initialize(IEntitySystemManager sysManager)
|
||||
@@ -122,7 +116,7 @@ public sealed partial class ShipFireGunsOperator : HTNOperator, IHtnConditionalS
|
||||
if (comp == null)
|
||||
return HTNOperatorStatus.Finished;
|
||||
|
||||
if (target.EntityId == EntityUid.Invalid || !xform.Coordinates.TryDistance(_entManager, target, out var distance) || distance > MaxTargetingRange)
|
||||
if (target.EntityId == EntityUid.Invalid)
|
||||
return HTNOperatorStatus.Finished;
|
||||
|
||||
if (ShutdownState == HTNPlanState.PlanFinished)
|
||||
|
||||
@@ -10,5 +10,12 @@ public sealed partial class ShipNpcTargetComponent : Component
|
||||
public bool NeedPower = false;
|
||||
|
||||
[DataField]
|
||||
public bool NeedGrid = true;
|
||||
public NpcTargetGridMode NeedGrid = NpcTargetGridMode.OnGrid;
|
||||
}
|
||||
|
||||
public enum NpcTargetGridMode
|
||||
{
|
||||
OnGrid,
|
||||
Either,
|
||||
NoGrid
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed partial class ShipSteererComponent : Component
|
||||
/// Try to evade collisions this far into the future even if stationary.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float BaseEvasionTime = 10f;
|
||||
public float BaseEvasionTime = 4f;
|
||||
|
||||
/// <summary>
|
||||
/// How unwilling we are to use brake to adjust our velocity. Higher means less willing.
|
||||
@@ -88,7 +88,7 @@ public sealed partial class ShipSteererComponent : Component
|
||||
/// How much to enlarge grid search bounds for collision evasion.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float GridSearchBuffer = 192f;
|
||||
public float GridSearchBuffer = 312f;
|
||||
|
||||
/// <summary>
|
||||
/// How much to enlarge grid search forward distance for collision evasion.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._Mono.Projectiles.TargetSeeking;
|
||||
using Content.Server.Physics.Controllers;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared._Mono;
|
||||
@@ -19,6 +20,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
[Dependency] private readonly MoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly TargetSeekingSystem _seeking = default!;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<ProjectileGridPhaseComponent> _phaseQuery;
|
||||
@@ -227,11 +229,12 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
var avoidanceVec = CalculateAvoidanceVector(ctx, config, brakeCtx, navVec);
|
||||
|
||||
// use avoidance vector if available or proceed with thrust as normal
|
||||
var wasNav = navVec;
|
||||
var wishInputVec = avoidanceVec ?? navVec;
|
||||
|
||||
var rotWish = wishInputVec;
|
||||
if (avoidanceVec != null && config.AvoidanceNoRotate)
|
||||
rotWish = CalculateNavigationVector(ctx, brakeCtx);
|
||||
rotWish = wasNav;
|
||||
|
||||
// process angular input
|
||||
var rotControl = CalculateRotationControl(ctx, config, rotWish, ref rotationCompensation);
|
||||
@@ -251,7 +254,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
{
|
||||
// check our brake thrust
|
||||
var brakeVec = GetGoodThrustVector((-ctx.ShipNorthAngle).RotateVec(-ctx.ShipBody.LinearVelocity), ctx.Shuttle);
|
||||
var brakeAccelVec = _mover.GetDirectionAccel(brakeVec, ctx.Shuttle, ctx.ShipBody);
|
||||
var brakeAccelVec = _mover.GetDirectionAccel(brakeVec, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
|
||||
var brakeAccel = brakeAccelVec.Length();
|
||||
|
||||
var linVelLenSq = ctx.ShipBody.LinearVelocity.LengthSquared();
|
||||
@@ -344,35 +347,30 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
var simTime = brake.BrakeAccel == 0f ? 10f : 2f * ctx.ShipBody.LinearVelocity.Length() / brake.BrakeAccel;
|
||||
simTime += config.BaseEvasionTime;
|
||||
|
||||
var forwardAccelVec = _mover.GetDirectionAccel(new Vector2(0f, 1f), ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
|
||||
var forwardAccelDir = NormalizedOrZero(forwardAccelVec);
|
||||
var forwardAccel = forwardAccelVec.Length();
|
||||
|
||||
_sectors.Clear();
|
||||
for (var i = 0; i < config.EvasionSectorCount; i++)
|
||||
{
|
||||
var angle = Angle.FromDegrees(360f * i / (float)config.EvasionSectorCount);
|
||||
var dir = angle.ToVec();
|
||||
|
||||
var rotated = (-ctx.ShipNorthAngle).RotateVec(dir);
|
||||
var dirAccel = _mover.GetDirectionAccel(rotated, ctx.Shuttle, ctx.ShipBody).Length();
|
||||
// var dirMax = _mover.ObtainMaxVel(rotated, ctx.Shuttle, ctx.ShipBody).Length();
|
||||
// if it's zero use a very rough approximation using our forward thrust
|
||||
if (dirAccel == 0f)
|
||||
{
|
||||
var upVec = new Vector2(0f, 1f);
|
||||
var penalty = 0.5f * (Vector2.Dot(upVec, rotated) + 1f);
|
||||
dirAccel = _mover.GetDirectionAccel(upVec, ctx.Shuttle, ctx.ShipBody).Length() * penalty;
|
||||
// dirMax = _mover.ObtainMaxVel(upVec, ctx.Shuttle, ctx.ShipBody).Length();
|
||||
var dirAccel = _mover.GetWorldDirectionAccel(dir, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
|
||||
if (dirAccel.LengthSquared() == 0f) {
|
||||
dirAccel = dir * forwardAccel * (Vector2.Dot(dir, forwardAccelDir) + 1) * 0.5f;
|
||||
}
|
||||
|
||||
for (var depth = 1; depth <= config.EvasionSectorDepth; depth++)
|
||||
{
|
||||
if (i % depth == 0)
|
||||
_sectors.Add(new(dir, dirAccel / depth, 1f / depth));
|
||||
_sectors.Add(new(dirAccel / depth, 1f / depth));
|
||||
}
|
||||
}
|
||||
// set scale to -1 to mark it as the wish-sector
|
||||
var wishRotated = (-ctx.ShipNorthAngle).RotateVec(wishDir);
|
||||
var wishDirThrust = _mover.GetDirectionAccel(wishRotated, ctx.Shuttle, ctx.ShipBody).Length();
|
||||
// var wishDirMaxVel = _mover.ObtainMaxVel(wishRotated, ctx.Shuttle, ctx.ShipBody).Length();
|
||||
_sectors.Add(new(wishDir, wishDirThrust, -1f));
|
||||
var wishDirThrust = _mover.GetWorldDirectionAccel(wishDir, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
|
||||
_sectors.Add(new(wishDirThrust, -1f));
|
||||
|
||||
foreach (var obstacle in _avoidEnts)
|
||||
{
|
||||
@@ -384,65 +382,88 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
var relVel = shipVel - obsVel;
|
||||
var toObsVec = obsPos - shipPos;
|
||||
var toObsDir = toObsVec.Normalized();
|
||||
// TODO: narrowphase avoidance if we overlap
|
||||
var obsDistance = MathF.Max(toObsVec.Length() - sumRadius, 1f);
|
||||
|
||||
var obsAccel = Vector2.Zero;
|
||||
if (_shuttleQuery.TryComp(obstacle.Ent, out var obsShuttle))
|
||||
obsAccel = obsShuttle.LastThrust;
|
||||
// Distances to bounding planes
|
||||
var obsDistanceFront = MathF.Max(toObsVec.Length() - sumRadius, 1f);
|
||||
var obsDistanceCenter = toObsVec.Length();
|
||||
|
||||
// get time-to-collide with the accel of each sector
|
||||
//
|
||||
// r = obsDistance
|
||||
// d = sumRadius
|
||||
// p = vt + at^2 / 2
|
||||
// solve for: dot(p, toObsDir) = r
|
||||
// condition for no hit: abs(dot(p, toObsVec.rotate(90))) > d
|
||||
// p = (x, y)
|
||||
// toObsDir = (u, v)
|
||||
// ux + vy = r
|
||||
// x = v_x*t + a_x * t^2 / 2
|
||||
// y = v_y*t + a_y * t^2 / 2
|
||||
// u(v_x*t + 0.5*a_x*t^2) + v(v_y*t + 0.5*a_y*t^2) = r
|
||||
// t^2 * (0.5*(u*a_x + v*a_y)) + t * (u*v_x + v*v_y) - r = 0
|
||||
// k = 0.5 * u*a_x + v*a_y = 0.5 * dot(toObsDir, a)
|
||||
// l = u*v_x + v*v_y = dot(toObsDir, vel)
|
||||
// m = -r
|
||||
// t = (-l + sqrt(l^2 - 4km)) / (2k)
|
||||
// if 4km > l^2, no hit
|
||||
// if t<0, no hit
|
||||
//
|
||||
// https://www.desmos.com/calculator/foyraxlzs7 graphed version
|
||||
var l = Vector2.Dot(toObsDir, relVel);
|
||||
for (var i = 0; i < _sectors.Count; i++)
|
||||
{
|
||||
var sector = _sectors[i];
|
||||
|
||||
var aDir = sector.Sector;
|
||||
var accel = aDir * sector.Accel - obsAccel; // account for relative accel
|
||||
var accel = sector.Accel;
|
||||
var k = 0.5f * Vector2.Dot(toObsDir, accel);
|
||||
var m = -obsDistance;
|
||||
float t;
|
||||
if (k*k < l*l / 1024f)
|
||||
t = l != 0f ? -m / l : -1f;
|
||||
|
||||
// 1. Solve crossing the Front Tangent Plane (Entering the obstacle bounds)
|
||||
float t_f;
|
||||
if (k * k < l * l / 1024f)
|
||||
t_f = l != 0f ? obsDistanceFront / l : -1f;
|
||||
else
|
||||
t = 4*k*m > l*l || k == 0f ? -1f : ((-l + MathF.Sqrt(l*l - 4*k*m)) * 0.5f / k);
|
||||
if (t < 0f || t > simTime)
|
||||
{
|
||||
var desc_f = l * l + 4f * k * obsDistanceFront;
|
||||
t_f = desc_f < 0f || k == 0f ? -1f : ((-l + MathF.Sqrt(desc_f)) * 0.5f / k);
|
||||
}
|
||||
|
||||
if (t_f < 0f || t_f > simTime)
|
||||
continue;
|
||||
|
||||
t = MathF.Max(0f, t - ctx.FrameTime);
|
||||
// 2. Resolve longitudinal exit/stop trajectory
|
||||
Vector2 p_end;
|
||||
if (k * k < l * l / 1024f)
|
||||
{
|
||||
float t_c = l != 0f ? obsDistanceCenter / l : -1f;
|
||||
if (t_c < 0f) t_c = t_f; // Failsafe bounds
|
||||
p_end = relVel * t_c + 0.5f * accel * t_c * t_c;
|
||||
}
|
||||
else
|
||||
{
|
||||
var desc_c = l * l + 4f * k * obsDistanceCenter;
|
||||
if (desc_c < 0f)
|
||||
{
|
||||
// The ship stops longitudinally inside the front half of the obstacle
|
||||
var t_stop = -l / (2f * k);
|
||||
p_end = relVel * t_stop + 0.5f * accel * t_stop * t_stop;
|
||||
}
|
||||
else
|
||||
{
|
||||
var t_c = ((-l + MathF.Sqrt(desc_c)) * 0.5f / k);
|
||||
if (t_c < 0f) t_c = t_f; // Failsafe bounds
|
||||
p_end = relVel * t_c + 0.5f * accel * t_c * t_c;
|
||||
}
|
||||
}
|
||||
|
||||
var endAt = relVel*t + 0.5f*accel*t*t;
|
||||
var proj = MathF.Abs(Vector2.Dot(endAt, new Vector2(-toObsDir.Y, toObsDir.X)));
|
||||
// Log.Info($"Avoid dir {aDir} time {t}, proj {proj} (k l m {k} {l} {m}) accel {accel}");
|
||||
if (proj > sumRadius)
|
||||
// 3. Line-segment to Circle-Center intersection.
|
||||
// Represents exact path traveled while navigating across the physical dimension of the obstacle.
|
||||
var p_start = relVel * t_f + 0.5f * accel * t_f * t_f;
|
||||
var seg = p_end - p_start;
|
||||
var l2 = seg.LengthSquared();
|
||||
var hits = false;
|
||||
|
||||
if (l2 == 0f)
|
||||
{
|
||||
if ((p_start - toObsVec).LengthSquared() <= sumRadius * sumRadius)
|
||||
hits = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find closest point on the segment to the obstacle's actual center point
|
||||
var t_seg = Math.Clamp(Vector2.Dot(toObsVec - p_start, seg) / l2, 0f, 1f);
|
||||
var proj = p_start + seg * t_seg;
|
||||
if ((proj - toObsVec).LengthSquared() <= sumRadius * sumRadius)
|
||||
hits = true;
|
||||
}
|
||||
|
||||
if (!hits)
|
||||
continue;
|
||||
|
||||
var t = MathF.Max(0f, t_f - ctx.FrameTime);
|
||||
|
||||
var ctime = sector.ImpactTime;
|
||||
if ((ctime == null || ctime > t) && (!sector.Priority || obstacle.IsGrid))
|
||||
{
|
||||
var priority = obstacle.IsGrid || sector.Priority;
|
||||
_sectors[i] = new(sector.Sector, sector.Accel, sector.Scale, t, priority);
|
||||
_sectors[i] = new(sector.Accel, sector.Scale, t, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,7 +478,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
var sector = _sectors[i];
|
||||
if (sector.ImpactTime == null)
|
||||
{
|
||||
var toWishSq = (wishDir - sector.Sector).LengthSquared();
|
||||
var toWishSq = (wishDir - NormalizedOrZero(sector.Accel) * sector.Scale).LengthSquared();
|
||||
if (toWishSq < closestDistance)
|
||||
{
|
||||
closestDistance = toWishSq;
|
||||
@@ -480,7 +501,7 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
if (chosen.Scale == -1f)
|
||||
return null;
|
||||
|
||||
return chosen.Sector * chosen.Scale;
|
||||
return NormalizedOrZero(chosen.Accel) * chosen.Scale;
|
||||
}
|
||||
|
||||
// navigation for if we aren't avoiding a collision
|
||||
@@ -501,21 +522,19 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
if (brake.LeftoverBrakePath > destDistance)
|
||||
return -linVelDir;
|
||||
|
||||
// mirror linVelDir in relation to toTargetDir
|
||||
var adjustVec = -(linVelDir - toDestDir * Vector2.Dot(linVelDir, toDestDir));
|
||||
var adjustDir = NormalizedOrZero(adjustVec);
|
||||
|
||||
var wishThrustDir = toDestDir + 2f * adjustVec;
|
||||
|
||||
var wishThrustVec = _mover.GetDirectionAccel((-ctx.ShipNorthAngle).RotateVec(wishThrustDir), ctx.Shuttle, ctx.ShipBody);
|
||||
var adjustAccel = Vector2.Dot(adjustDir, wishThrustVec);
|
||||
|
||||
var maxAdjust = Vector2.Dot(-adjustDir, relVel);
|
||||
|
||||
adjustVec *= adjustAccel == 0f ? 0f : float.Clamp(maxAdjust / (adjustAccel * ctx.FrameTime), 0f, 1f);
|
||||
var accelEstVec = _mover.GetWorldDirectionAccel(toDestVec, ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
|
||||
var accelEst = accelEstVec.Length();
|
||||
// fallback to forward accel
|
||||
if (accelEst == 0f)
|
||||
{
|
||||
var forwardAccelVec = _mover.GetDirectionAccel(new Vector2(0f, 1f), ctx.Shuttle, ctx.ShipBody, ctx.ShipXform);
|
||||
accelEst = forwardAccelVec.Length();
|
||||
}
|
||||
// takes target relative to us
|
||||
var wishAngle = _seeking.CalculateAdvancedTracking(toDestVec, -relVel, accelEst);
|
||||
|
||||
// do not yet process whether we can actually accelerate well in that direction
|
||||
return toDestDir + 2f * adjustVec;
|
||||
return wishAngle.ToWorldVec();
|
||||
}
|
||||
|
||||
private readonly record struct RotationResult(float RotationInput, float WishAngleVel);
|
||||
@@ -714,5 +733,5 @@ public sealed partial class ShipSteeringSystem : EntitySystem
|
||||
|
||||
private readonly record struct ObstacleCandidate(Entity<TransformComponent, PhysicsComponent> Ent, Vector2 Pos, float Radius, bool IsGrid);
|
||||
|
||||
private record struct EvadeCandidate(Vector2 Sector, float Accel, float Scale, float? ImpactTime = null, bool Priority = false);
|
||||
private record struct EvadeCandidate(Vector2 Accel, float Scale, float? ImpactTime = null, bool Priority = false);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ public sealed partial class ShipTargetingComponent : Component
|
||||
[DataField]
|
||||
public float LeadingAccuracy = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// How good to lead offgrid targets.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float OffgridLeadingAccuracy = 1f; // be more accurate since an offgrid target is presumably maneurable and small
|
||||
|
||||
/// <summary>
|
||||
/// Velocity we're currently estimating for imperfect target leading.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user