Merge pull request #1893 from deltanedas/upstream-ops
upstream merge but real
This commit is contained in:
commit
fe93e6b9c1
|
|
@ -41,21 +41,10 @@ jobs:
|
|||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
- name: Upload build artifact
|
||||
id: artifact-upload-step
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: release/*.zip
|
||||
compression-level: 0
|
||||
retention-days: 0
|
||||
|
||||
- name: Publish version
|
||||
run: Tools/publish_github_artifact.py
|
||||
run: Tools/publish_multi_request.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }}
|
||||
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
|
||||
|
||||
- name: Publish changelog (Discord)
|
||||
|
|
@ -68,8 +57,3 @@ jobs:
|
|||
run: Tools/actions_changelog_rss.py
|
||||
env:
|
||||
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
|
||||
|
||||
- uses: geekyeggo/delete-artifact@v5
|
||||
if: always()
|
||||
with:
|
||||
name: build
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ namespace Content.Client.Actions
|
|||
continue;
|
||||
|
||||
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||
var actionId = Spawn(null);
|
||||
var actionId = Spawn();
|
||||
AddComp(actionId, action);
|
||||
AddActionDirect(user, actionId);
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ namespace Content.Client.Actions.UI
|
|||
{
|
||||
var duration = Cooldown.Value.End - Cooldown.Value.Start;
|
||||
|
||||
if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
|
||||
if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup))
|
||||
return;
|
||||
|
||||
_cooldownLabel.SetMessage(markup);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration.UI.SetOutfit
|
||||
|
|
@ -65,9 +64,18 @@ namespace Content.Client.Administration.UI.SetOutfit
|
|||
PopulateByFilter(SearchBar.Text);
|
||||
}
|
||||
|
||||
private IEnumerable<StartingGearPrototype> GetPrototypes()
|
||||
{
|
||||
// Filter out any StartingGearPrototypes that belong to loadouts
|
||||
var loadouts = _prototypeManager.EnumeratePrototypes<LoadoutPrototype>();
|
||||
var loadoutGears = loadouts.Select(l => l.StartingGear);
|
||||
return _prototypeManager.EnumeratePrototypes<StartingGearPrototype>()
|
||||
.Where(p => !loadoutGears.Contains(p.ID));
|
||||
}
|
||||
|
||||
private void PopulateList()
|
||||
{
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
foreach (var gear in GetPrototypes())
|
||||
{
|
||||
OutfitList.Add(GetItem(gear, OutfitList));
|
||||
}
|
||||
|
|
@ -76,7 +84,7 @@ namespace Content.Client.Administration.UI.SetOutfit
|
|||
private void PopulateByFilter(string filter)
|
||||
{
|
||||
OutfitList.Clear();
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
foreach (var gear in GetPrototypes())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filter) &&
|
||||
gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
|||
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete);
|
||||
}
|
||||
|
||||
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
|
||||
{
|
||||
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
|
||||
|
|
@ -75,4 +76,13 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<AnomalySupercriticalComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.Scale = Vector2.One;
|
||||
sprite.Color = sprite.Color.WithAlpha(1f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Anomaly.Effects;
|
||||
|
||||
public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
|
||||
}
|
||||
|
||||
private void OnAfterHandleState(Entity<InnerBodyAnomalyComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
if (ent.Comp.FallbackSprite is null)
|
||||
return;
|
||||
|
||||
if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index))
|
||||
index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap);
|
||||
|
||||
if (TryComp<BodyComponent>(ent, out var body) &&
|
||||
body.Prototype is not null &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite))
|
||||
{
|
||||
sprite.LayerSetSprite(index, speciesSprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetSprite(index, ent.Comp.FallbackSprite);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(index, true);
|
||||
sprite.LayerSetShader(index, "unshaded");
|
||||
}
|
||||
|
||||
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
var index = sprite.LayerMapGet(ent.Comp.LayerMap);
|
||||
sprite.LayerSetVisible(index, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -306,6 +306,9 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
|||
.WithMaxDistance(comp.Range);
|
||||
|
||||
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
|
||||
playingCount++;
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
|||
if(!_adminAudioEnabled) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_adminAudio.Add(stream.Value.Entity);
|
||||
_adminAudio.Add(stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
|
||||
|
|
@ -76,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
|||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_eventAudio.Add(soundEvent.Type, stream.Value.Entity);
|
||||
_eventAudio.Add(soundEvent.Type, stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
|
||||
|
|
|
|||
|
|
@ -213,9 +213,9 @@ public sealed partial class ContentAudioSystem
|
|||
false,
|
||||
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
|
||||
|
||||
_ambientMusicStream = strim.Value.Entity;
|
||||
_ambientMusicStream = strim?.Entity;
|
||||
|
||||
if (_musicProto.FadeIn)
|
||||
if (_musicProto.FadeIn && strim != null)
|
||||
{
|
||||
FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ public sealed partial class ContentAudioSystem
|
|||
false,
|
||||
_lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
|
||||
);
|
||||
if (playResult.Value.Entity == default)
|
||||
if (playResult == null)
|
||||
{
|
||||
_sawmill.Warning(
|
||||
$"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
using System.IO;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Actions;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Console;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
||||
|
|
@ -50,7 +46,7 @@ public sealed class LoadActionsCommand : LocalizedCommands
|
|||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
LoadActs(); // DeltaV - Load from a file dialogue instead
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -63,48 +59,4 @@ public sealed class LoadActionsCommand : LocalizedCommands
|
|||
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Load actions from a file stream instead
|
||||
/// </summary>
|
||||
private static async void LoadActs()
|
||||
{
|
||||
var fileMan = IoCManager.Resolve<IFileDialogManager>();
|
||||
var actMan = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ActionsSystem>();
|
||||
|
||||
var stream = await fileMan.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
|
||||
if (stream is null)
|
||||
return;
|
||||
|
||||
var reader = new StreamReader(stream);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
actMan.LoadActionAssignments(yamlStream);
|
||||
reader.Close();
|
||||
}
|
||||
}
|
||||
|
||||
[AnyCommand]
|
||||
public sealed class LoadMappingActionsCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
public const string CommandName = "loadmapacts";
|
||||
|
||||
public override string Command => CommandName;
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MappingSystem>().LoadMappingActions();
|
||||
}
|
||||
catch
|
||||
{
|
||||
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using Content.Client.Mapping;
|
||||
using Content.Client.Markers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
|
@ -10,6 +12,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
|||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
|
||||
public override string Command => "mappingclientsidesetup";
|
||||
|
||||
|
|
@ -21,8 +24,8 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
|||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
shell.ExecuteCommand(ShowSubFloorForever.CommandName);
|
||||
shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
|
||||
shell.ExecuteCommand("showsubfloorforever");
|
||||
_stateManager.RequestStateChange<MappingState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Numerics;
|
|||
using System.Threading;
|
||||
using Content.Client.CombatMode;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Mapping;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
|
@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI
|
|||
/// <remarks>
|
||||
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
|
||||
/// </remarks>
|
||||
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>
|
||||
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>, IOnStateEntered<MappingState>, IOnStateExited<MappingState>
|
||||
{
|
||||
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
|
|
@ -42,18 +43,51 @@ namespace Content.Client.ContextMenu.UI
|
|||
public Action<ContextMenuElement>? OnSubMenuOpened;
|
||||
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
|
||||
|
||||
private bool _setup;
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public void OnStateEntered(MappingState state)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
public void OnStateExited(MappingState state)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
if (_setup)
|
||||
return;
|
||||
|
||||
_setup = true;
|
||||
|
||||
RootMenu = new(this, null);
|
||||
RootMenu.OnPopupHide += Close;
|
||||
Menus.Push(RootMenu);
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
public void Shutdown()
|
||||
{
|
||||
if (!_setup)
|
||||
return;
|
||||
|
||||
_setup = false;
|
||||
|
||||
Close();
|
||||
RootMenu.OnPopupHide -= Close;
|
||||
RootMenu.Dispose();
|
||||
RootMenu = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Robust.Client.Graphics;
|
|||
using Robust.Client.Input;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Decals.Overlays;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
|||
private readonly SharedTransformSystem _transform;
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
|
||||
|
||||
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
|
||||
{
|
||||
|
|
@ -24,6 +25,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
|||
_placement = placement;
|
||||
_transform = transform;
|
||||
_sprite = sprite;
|
||||
ZIndex = 1000;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
|
@ -55,7 +57,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
|||
|
||||
if (snap)
|
||||
{
|
||||
localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector;
|
||||
localPos = localPos.Floored() + grid.TileSizeHalfVector;
|
||||
}
|
||||
|
||||
// Nothing uses snap cardinals so probably don't need preview?
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
|
@ -13,15 +17,8 @@ using Robust.Client.UserInterface.Controls;
|
|||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Robust.Client;
|
||||
using static Content.Shared.Interaction.SharedInteractionSystem;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
|
||||
namespace Content.Client.Examine
|
||||
|
|
@ -38,7 +35,6 @@ namespace Content.Client.Examine
|
|||
|
||||
private EntityUid _examinedEntity;
|
||||
private EntityUid _lastExaminedEntity;
|
||||
private EntityUid _playerEntity;
|
||||
private Popup? _examineTooltipOpen;
|
||||
private ScreenCoordinates _popupPos;
|
||||
private CancellationTokenSource? _requestCancelTokenSource;
|
||||
|
|
@ -77,9 +73,9 @@ namespace Content.Client.Examine
|
|||
public override void Update(float frameTime)
|
||||
{
|
||||
if (_examineTooltipOpen is not {Visible: true}) return;
|
||||
if (!_examinedEntity.Valid || !_playerEntity.Valid) return;
|
||||
if (!_examinedEntity.Valid || _playerManager.LocalEntity is not { } player) return;
|
||||
|
||||
if (!CanExamine(_playerEntity, _examinedEntity))
|
||||
if (!CanExamine(player, _examinedEntity))
|
||||
CloseTooltip();
|
||||
}
|
||||
|
||||
|
|
@ -117,9 +113,8 @@ namespace Content.Client.Examine
|
|||
return false;
|
||||
}
|
||||
|
||||
_playerEntity = _playerManager.LocalEntity ?? default;
|
||||
|
||||
if (_playerEntity == default || !CanExamine(_playerEntity, entity))
|
||||
if (_playerManager.LocalEntity is not { } player ||
|
||||
!CanExamine(player, entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -360,10 +355,7 @@ namespace Content.Client.Examine
|
|||
|
||||
FormattedMessage message;
|
||||
|
||||
// Basically this just predicts that we can't make out the entity if we have poor vision.
|
||||
var canSeeClearly = !HasComp<BlurryVisionComponent>(playerEnt);
|
||||
|
||||
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly);
|
||||
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false);
|
||||
|
||||
// Always update tooltip info from client first.
|
||||
// If we get it wrong, server will correct us later anyway.
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
|
||||
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
|
||||
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
|
||||
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
|
||||
<RichTextLabel Name="NameLabel" SetWidth="150" />
|
||||
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ namespace Content.Client.HealthAnalyzer.UI
|
|||
// Patient Information
|
||||
|
||||
SpriteView.SetEntity(target.Value);
|
||||
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
|
||||
NoDataTex.Visible = !SpriteView.Visible;
|
||||
|
||||
var name = new FormattedMessage();
|
||||
name.PushColor(Color.White);
|
||||
|
|
|
|||
|
|
@ -4,23 +4,23 @@ using Content.Client.Chat.Managers;
|
|||
using Content.Client.Clickable;
|
||||
using Content.Client.DebugMon;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.Fullscreen;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Launcher;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Replay;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.Fullscreen;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Replay;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
|
||||
|
||||
namespace Content.Client.IoC
|
||||
{
|
||||
internal static class ClientContentIoC
|
||||
|
|
@ -49,6 +49,7 @@ namespace Content.Client.IoC
|
|||
collection.Register<DocumentParsingManager>();
|
||||
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
||||
collection.Register<MappingManager>();
|
||||
collection.Register<DebugMonitorManager>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<mapping:MappingActionsButton
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping"
|
||||
StyleClasses="ButtonSquare" ToggleMode="True" SetSize="32 32" Margin="0 0 5 0"
|
||||
TooltipDelay="0">
|
||||
<TextureRect Name="Texture" Access="Public" Stretch="Scale" SetSize="16 16"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</mapping:MappingActionsButton>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingActionsButton : Button
|
||||
{
|
||||
public MappingActionsButton()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<mapping:MappingDoNotMeasure
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
</mapping:MappingDoNotMeasure>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingDoNotMeasure : Control
|
||||
{
|
||||
public MappingDoNotMeasure()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Mapping;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IFileDialogManager _file = default!;
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
|
||||
private Stream? _saveStream;
|
||||
private MappingMapDataMessage? _mapData;
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_net.RegisterNetMessage<MappingSaveMapMessage>();
|
||||
_net.RegisterNetMessage<MappingSaveMapErrorMessage>(OnSaveError);
|
||||
_net.RegisterNetMessage<MappingMapDataMessage>(OnMapData);
|
||||
}
|
||||
|
||||
private void OnSaveError(MappingSaveMapErrorMessage message)
|
||||
{
|
||||
_saveStream?.DisposeAsync();
|
||||
_saveStream = null;
|
||||
}
|
||||
|
||||
private async void OnMapData(MappingMapDataMessage message)
|
||||
{
|
||||
if (_saveStream == null)
|
||||
{
|
||||
_mapData = message;
|
||||
return;
|
||||
}
|
||||
|
||||
await _saveStream.WriteAsync(Encoding.ASCII.GetBytes(message.Yml));
|
||||
await _saveStream.DisposeAsync();
|
||||
|
||||
_saveStream = null;
|
||||
_mapData = null;
|
||||
}
|
||||
|
||||
public async Task SaveMap()
|
||||
{
|
||||
if (_saveStream != null)
|
||||
await _saveStream.DisposeAsync();
|
||||
|
||||
var request = new MappingSaveMapMessage();
|
||||
_net.ClientSendMessage(request);
|
||||
|
||||
var path = await _file.SaveFile();
|
||||
if (path is not { fileStream: var stream })
|
||||
return;
|
||||
|
||||
if (_mapData != null)
|
||||
{
|
||||
await stream.WriteAsync(Encoding.ASCII.GetBytes(_mapData.Yml));
|
||||
_mapData = null;
|
||||
await stream.FlushAsync();
|
||||
await stream.DisposeAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_saveStream = stream;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Client.Mapping.MappingState;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
|
||||
// 1 off in case something else uses these colors since we use them to compare
|
||||
private static readonly Color PickColor = new(1, 255, 0);
|
||||
private static readonly Color DeleteColor = new(255, 1, 0);
|
||||
|
||||
private readonly Dictionary<EntityUid, Color> _oldColors = new();
|
||||
|
||||
private readonly MappingState _state;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public MappingOverlay(MappingState state)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_state = state;
|
||||
_shader = _prototypes.Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
foreach (var (id, color) in _oldColors)
|
||||
{
|
||||
if (!_entities.TryGetComponent(id, out SpriteComponent? sprite))
|
||||
continue;
|
||||
|
||||
if (sprite.Color == DeleteColor || sprite.Color == PickColor)
|
||||
sprite.Color = color;
|
||||
}
|
||||
|
||||
_oldColors.Clear();
|
||||
|
||||
if (_player.LocalEntity == null)
|
||||
return;
|
||||
|
||||
var handle = args.WorldHandle;
|
||||
handle.UseShader(_shader);
|
||||
|
||||
switch (_state.State)
|
||||
{
|
||||
case CursorState.Pick:
|
||||
{
|
||||
if (_state.GetHoveredEntity() is { } entity &&
|
||||
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
_oldColors[entity] = sprite.Color;
|
||||
sprite.Color = PickColor;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CursorState.Delete:
|
||||
{
|
||||
if (_state.GetHoveredEntity() is { } entity &&
|
||||
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
_oldColors[entity] = sprite.Color;
|
||||
sprite.Color = DeleteColor;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// Used to represent a button's data in the mapping editor.
|
||||
/// </summary>
|
||||
public sealed class MappingPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// The prototype instance, if any.
|
||||
/// Can be one of <see cref="EntityPrototype"/>, <see cref="ContentTileDefinition"/> or <see cref="DecalPrototype"/>
|
||||
/// If null, this is a top-level button (such as Entities, Tiles or Decals)
|
||||
/// </summary>
|
||||
public readonly IPrototype? Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// The text to display on the UI for this button.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Which other prototypes (buttons) this one is nested inside of.
|
||||
/// </summary>
|
||||
public List<MappingPrototype>? Parents;
|
||||
|
||||
/// <summary>
|
||||
/// Which other prototypes (buttons) are nested inside this one.
|
||||
/// </summary>
|
||||
public List<MappingPrototype>? Children;
|
||||
|
||||
public MappingPrototype(IPrototype? prototype, string name)
|
||||
{
|
||||
Prototype = prototype;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<mapping:MappingPrototypeList
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="CollapseAllButton" Access="Public" Text="-" SetSize="48 48"
|
||||
StyleClasses="ButtonSquare" ToolTip="Collapse All" TooltipDelay="0" />
|
||||
<LineEdit Name="SearchBar" SetHeight="48" HorizontalExpand="True" Access="Public" />
|
||||
<Button Name="ClearSearchButton" Access="Public" Text="X" SetSize="48 48"
|
||||
StyleClasses="ButtonSquare" />
|
||||
</BoxContainer>
|
||||
<ScrollContainer Name="ScrollContainer" Access="Public" VerticalExpand="True"
|
||||
ReserveScrollbarSpace="True">
|
||||
<BoxContainer Name="PrototypeList" Access="Public" Orientation="Vertical" />
|
||||
<PrototypeListContainer Name="SearchList" Access="Public" Visible="False" />
|
||||
</ScrollContainer>
|
||||
<mapping:MappingDoNotMeasure Visible="False">
|
||||
<mapping:MappingSpawnButton Name="MeasureButton" Access="Public" />
|
||||
</mapping:MappingDoNotMeasure>
|
||||
</BoxContainer>
|
||||
</mapping:MappingPrototypeList>
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingPrototypeList : Control
|
||||
{
|
||||
private (int start, int end) _lastIndices;
|
||||
private readonly List<MappingPrototype> _prototypes = new();
|
||||
private readonly List<Texture> _insertTextures = new();
|
||||
private readonly List<MappingPrototype> _search = new();
|
||||
|
||||
public MappingSpawnButton? Selected;
|
||||
public Action<IPrototype, List<Texture>>? GetPrototypeData;
|
||||
public event Action<MappingSpawnButton, IPrototype?>? SelectionChanged;
|
||||
public event Action<MappingSpawnButton, ButtonToggledEventArgs>? CollapseToggled;
|
||||
|
||||
public MappingPrototypeList()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
MeasureButton.Measure(Vector2Helpers.Infinity);
|
||||
|
||||
ScrollContainer.OnScrolled += UpdateSearch;
|
||||
OnResized += UpdateSearch;
|
||||
}
|
||||
|
||||
public void UpdateVisible(List<MappingPrototype> prototypes)
|
||||
{
|
||||
_prototypes.Clear();
|
||||
|
||||
PrototypeList.DisposeAllChildren();
|
||||
|
||||
_prototypes.AddRange(prototypes);
|
||||
|
||||
Selected = null;
|
||||
ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
foreach (var prototype in _prototypes)
|
||||
{
|
||||
Insert(PrototypeList, prototype, true);
|
||||
}
|
||||
}
|
||||
|
||||
public MappingSpawnButton Insert(Container list, MappingPrototype mapping, bool includeChildren)
|
||||
{
|
||||
var prototype = mapping.Prototype;
|
||||
|
||||
_insertTextures.Clear();
|
||||
|
||||
if (prototype != null)
|
||||
GetPrototypeData?.Invoke(prototype, _insertTextures);
|
||||
|
||||
var button = new MappingSpawnButton { Prototype = mapping };
|
||||
button.Label.Text = mapping.Name;
|
||||
|
||||
if (_insertTextures.Count > 0)
|
||||
{
|
||||
button.Texture.Textures.AddRange(_insertTextures);
|
||||
button.Texture.InvalidateMeasure();
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Texture.Visible = false;
|
||||
}
|
||||
|
||||
if (prototype != null && button.Prototype == Selected?.Prototype)
|
||||
{
|
||||
Selected = button;
|
||||
button.Button.Pressed = true;
|
||||
}
|
||||
|
||||
list.AddChild(button);
|
||||
|
||||
button.Button.OnToggled += _ => SelectionChanged?.Invoke(button, prototype);
|
||||
|
||||
if (includeChildren && mapping.Children?.Count > 0)
|
||||
{
|
||||
button.CollapseButton.Visible = true;
|
||||
button.CollapseButton.OnToggled += args => CollapseToggled?.Invoke(button, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.CollapseButtonWrapper.Visible = false;
|
||||
button.CollapseButton.Visible = false;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public void Search(List<MappingPrototype> prototypes)
|
||||
{
|
||||
_search.Clear();
|
||||
SearchList.DisposeAllChildren();
|
||||
_lastIndices = (0, -1);
|
||||
|
||||
_search.AddRange(prototypes);
|
||||
SearchList.TotalItemCount = _search.Count;
|
||||
ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
UpdateSearch();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a virtual list where not all buttons exist at one time, since there may be thousands of them.
|
||||
/// </summary>
|
||||
private void UpdateSearch()
|
||||
{
|
||||
if (!SearchList.Visible)
|
||||
return;
|
||||
|
||||
var height = MeasureButton.DesiredSize.Y + PrototypeListContainer.Separation;
|
||||
var offset = Math.Max(-SearchList.Position.Y, 0);
|
||||
var startIndex = (int) Math.Floor(offset / height);
|
||||
SearchList.ItemOffset = startIndex;
|
||||
|
||||
var (prevStart, prevEnd) = _lastIndices;
|
||||
var endIndex = startIndex - 1;
|
||||
var spaceUsed = -height;
|
||||
|
||||
// calculate how far down we are scrolled
|
||||
while (spaceUsed < SearchList.Parent!.Height)
|
||||
{
|
||||
spaceUsed += height;
|
||||
endIndex += 1;
|
||||
}
|
||||
|
||||
endIndex = Math.Min(endIndex, _search.Count - 1);
|
||||
|
||||
// nothing changed in terms of which buttons are visible now and before
|
||||
if (endIndex == prevEnd && startIndex == prevStart)
|
||||
return;
|
||||
|
||||
_lastIndices = (startIndex, endIndex);
|
||||
|
||||
// remove previously seen but now unseen buttons from the top
|
||||
for (var i = prevStart; i < startIndex && i <= prevEnd; i++)
|
||||
{
|
||||
var control = SearchList.GetChild(0);
|
||||
SearchList.RemoveChild(control);
|
||||
}
|
||||
|
||||
// remove previously seen but now unseen buttons from the bottom
|
||||
for (var i = prevEnd; i > endIndex && i >= prevStart; i--)
|
||||
{
|
||||
var control = SearchList.GetChild(SearchList.ChildCount - 1);
|
||||
SearchList.RemoveChild(control);
|
||||
}
|
||||
|
||||
// insert buttons that can now be seen, from the start
|
||||
for (var i = Math.Min(prevStart - 1, endIndex); i >= startIndex; i--)
|
||||
{
|
||||
Insert(SearchList, _search[i], false).SetPositionInParent(0);
|
||||
}
|
||||
|
||||
// insert buttons that can now be seen, from the end
|
||||
for (var i = Math.Max(prevEnd + 1, startIndex); i <= endIndex; i++)
|
||||
{
|
||||
Insert(SearchList, _search[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<mapping:MappingScreen
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
|
||||
xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping"
|
||||
VerticalExpand="False"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Center">
|
||||
<controls:RecordedSplitContainer Name="ScreenContainer" HorizontalExpand="True"
|
||||
VerticalExpand="True" SplitWidth="0"
|
||||
StretchDirection="TopLeft">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Name="SpawnContainer" MinWidth="200" SetWidth="600">
|
||||
<mapping:MappingPrototypeList Name="Prototypes" Access="Public" VerticalExpand="True" />
|
||||
<BoxContainer Name="DecalContainer" Access="Public" Orientation="Horizontal"
|
||||
Visible="False">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<ColorSelectorSliders Name="DecalColorPicker" IsAlphaVisible="True" />
|
||||
<Button Name="DecalPickerOpen" Text="{Loc decal-placer-window-palette}"
|
||||
StyleClasses="ButtonSquare" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<CheckBox Name="DecalEnableAuto" Margin="0 0 0 10"
|
||||
Text="{Loc decal-placer-window-enable-auto}" />
|
||||
<CheckBox Name="DecalEnableSnap"
|
||||
Text="{Loc decal-placer-window-enable-snap}" />
|
||||
<CheckBox Name="DecalEnableCleanable"
|
||||
Text="{Loc decal-placer-window-enable-cleanable}" />
|
||||
<BoxContainer Name="DecalSpinBoxContainer" Orientation="Horizontal">
|
||||
<Label Text="{Loc decal-placer-window-rotation}" Margin="0 0 0 1" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc decal-placer-window-zindex}" Margin="0 0 0 1" />
|
||||
<SpinBox Name="DecalZIndexSpinBox" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="EntityContainer" Access="Public" Orientation="Horizontal"
|
||||
Visible="False">
|
||||
<Button Name="EntityReplaceButton" Access="Public" ToggleMode="True"
|
||||
SetHeight="48"
|
||||
StyleClasses="ButtonSquare" Text="{Loc 'mapping-replace'}" HorizontalExpand="True" />
|
||||
<OptionButton Name="EntityPlacementMode" Access="Public"
|
||||
SetHeight="48"
|
||||
StyleClasses="ButtonSquare" TooltipDelay="0"
|
||||
ToolTip="{Loc entity-spawn-window-override-menu-tooltip}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EraseEntityButton" Access="Public" HorizontalExpand="True"
|
||||
SetHeight="48"
|
||||
ToggleMode="True" Text="{Loc 'mapping-erase-entity'}" StyleClasses="ButtonSquare" />
|
||||
<Button Name="EraseDecalButton" Access="Public" HorizontalExpand="True"
|
||||
SetHeight="48"
|
||||
ToggleMode="True" Text="{Loc 'mapping-erase-decal'}" StyleClasses="ButtonSquare" />
|
||||
</BoxContainer>
|
||||
<widgets:ChatBox Visible="False" />
|
||||
</BoxContainer>
|
||||
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
|
||||
<controls:MainViewport Name="MainViewport"/>
|
||||
<hotbar:HotbarGui Name="Hotbar" />
|
||||
<PanelContainer Name="Actions" VerticalExpand="True" HorizontalExpand="True"
|
||||
MaxHeight="48">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#222222AA" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Horizontal" Margin="15 10">
|
||||
<mapping:MappingActionsButton
|
||||
Name="Add" Access="Public" Disabled="True" ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Fill" Access="Public"
|
||||
ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Grab" Access="Public"
|
||||
ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Move" Access="Public"
|
||||
ToolTip="" Visible="False" />
|
||||
<mapping:MappingActionsButton Name="Pick" Access="Public"
|
||||
ToolTip="Pick (Hold 5)" />
|
||||
<mapping:MappingActionsButton Name="Delete" Access="Public"
|
||||
ToolTip="Delete (Hold 6)" />
|
||||
<mapping:MappingActionsButton Name="Flip" Access="Public" ToggleMode="False"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</LayoutContainer>
|
||||
</controls:RecordedSplitContainer>
|
||||
</mapping:MappingScreen>
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Decals;
|
||||
using Content.Client.Decals.UI;
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
using Content.Shared.Decals;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingScreen : InGameScreen
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public DecalPlacementSystem DecalSystem = default!;
|
||||
|
||||
private PaletteColorPicker? _picker;
|
||||
|
||||
private ProtoId<DecalPrototype>? _id;
|
||||
private Color _decalColor = Color.White;
|
||||
private float _decalRotation;
|
||||
private bool _decalSnap;
|
||||
private int _decalZIndex;
|
||||
private bool _decalCleanable;
|
||||
|
||||
private bool _decalAuto;
|
||||
|
||||
public override ChatBox ChatBox => GetWidget<ChatBox>()!;
|
||||
|
||||
public event Func<MappingSpawnButton, bool>? IsDecalVisible;
|
||||
|
||||
public MappingScreen()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
AutoscaleMaxResolution = new Vector2i(1080, 770);
|
||||
|
||||
SetAnchorPreset(ScreenContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(ViewportContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(SpawnContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(MainViewport, LayoutPreset.Wide);
|
||||
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
|
||||
SetAnchorAndMarginPreset(Actions, LayoutPreset.TopWide, margin: 5);
|
||||
|
||||
ScreenContainer.OnSplitResizeFinished += () =>
|
||||
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));
|
||||
|
||||
var rotationSpinBox = new FloatSpinBox(90.0f, 0)
|
||||
{
|
||||
HorizontalExpand = true
|
||||
};
|
||||
DecalSpinBoxContainer.AddChild(rotationSpinBox);
|
||||
|
||||
DecalColorPicker.OnColorChanged += OnDecalColorPicked;
|
||||
DecalPickerOpen.OnPressed += OnDecalPickerOpenPressed;
|
||||
rotationSpinBox.OnValueChanged += args =>
|
||||
{
|
||||
_decalRotation = args.Value;
|
||||
UpdateDecal();
|
||||
};
|
||||
DecalEnableAuto.OnToggled += args =>
|
||||
{
|
||||
_decalAuto = args.Pressed;
|
||||
if (_id is { } id)
|
||||
SelectDecal(id);
|
||||
};
|
||||
DecalEnableSnap.OnToggled += args =>
|
||||
{
|
||||
_decalSnap = args.Pressed;
|
||||
UpdateDecal();
|
||||
};
|
||||
DecalEnableCleanable.OnToggled += args =>
|
||||
{
|
||||
_decalCleanable = args.Pressed;
|
||||
UpdateDecal();
|
||||
};
|
||||
DecalZIndexSpinBox.ValueChanged += args =>
|
||||
{
|
||||
_decalZIndex = args.Value;
|
||||
UpdateDecal();
|
||||
};
|
||||
|
||||
for (var i = 0; i < EntitySpawnWindow.InitOpts.Length; i++)
|
||||
{
|
||||
EntityPlacementMode.AddItem(EntitySpawnWindow.InitOpts[i], i);
|
||||
}
|
||||
|
||||
Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png";
|
||||
Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png";
|
||||
Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png";
|
||||
Flip.OnPressed += args => FlipSides();
|
||||
}
|
||||
|
||||
public void FlipSides()
|
||||
{
|
||||
ScreenContainer.Flip();
|
||||
|
||||
if (SpawnContainer.GetPositionInParent() == 0)
|
||||
{
|
||||
Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDecalColorPicked(Color color)
|
||||
{
|
||||
_decalColor = color;
|
||||
DecalColorPicker.Color = color;
|
||||
UpdateDecal();
|
||||
}
|
||||
|
||||
private void OnDecalPickerOpenPressed(ButtonEventArgs obj)
|
||||
{
|
||||
if (_picker == null)
|
||||
{
|
||||
_picker = new PaletteColorPicker();
|
||||
_picker.OpenToLeft();
|
||||
_picker.PaletteList.OnItemSelected += args =>
|
||||
{
|
||||
var color = ((Color?) args.ItemList.GetSelected().First().Metadata)!.Value;
|
||||
OnDecalColorPicked(color);
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_picker.IsOpen)
|
||||
_picker.Close();
|
||||
else
|
||||
_picker.Open();
|
||||
}
|
||||
|
||||
private void UpdateDecal()
|
||||
{
|
||||
if (_id is not { } id)
|
||||
return;
|
||||
|
||||
DecalSystem.UpdateDecalInfo(id, _decalColor, _decalRotation, _decalSnap, _decalZIndex, _decalCleanable);
|
||||
}
|
||||
|
||||
public void SelectDecal(string decalId)
|
||||
{
|
||||
if (!_prototype.TryIndex<DecalPrototype>(decalId, out var decal))
|
||||
return;
|
||||
|
||||
_id = decalId;
|
||||
|
||||
if (_decalAuto)
|
||||
{
|
||||
_decalColor = Color.White;
|
||||
_decalCleanable = decal.DefaultCleanable;
|
||||
_decalSnap = decal.DefaultSnap;
|
||||
|
||||
DecalColorPicker.Color = _decalColor;
|
||||
DecalEnableCleanable.Pressed = _decalCleanable;
|
||||
DecalEnableSnap.Pressed = _decalSnap;
|
||||
}
|
||||
|
||||
UpdateDecal();
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void RefreshList()
|
||||
{
|
||||
foreach (var control in Prototypes.Children)
|
||||
{
|
||||
if (control is not MappingSpawnButton button ||
|
||||
button.Prototype?.Prototype is not DecalPrototype)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var child in button.Children)
|
||||
{
|
||||
if (child is not MappingSpawnButton { Prototype.Prototype: DecalPrototype } childButton)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
childButton.Texture.Modulate = _decalColor;
|
||||
childButton.Visible = IsDecalVisible?.Invoke(childButton) ?? true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetChatSize(Vector2 size)
|
||||
{
|
||||
ScreenContainer.DesiredSplitCenter = size.X;
|
||||
ScreenContainer.ResizeMode = SplitContainer.SplitResizeMode.RespectChildrenMinSize;
|
||||
}
|
||||
|
||||
public void UnPressActionsExcept(Control except)
|
||||
{
|
||||
Add.Pressed = Add == except;
|
||||
Fill.Pressed = Fill == except;
|
||||
Grab.Pressed = Grab == except;
|
||||
Move.Pressed = Move == except;
|
||||
Pick.Pressed = Pick == except;
|
||||
Delete.Pressed = Delete == except;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<mapping:MappingSpawnButton
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Control>
|
||||
<Button Name="Button" Access="Public" ToggleMode="True" StyleClasses="ButtonSquare" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<LayeredTextureRect Name="Texture" Access="Public" MinSize="48 48"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Stretch="KeepAspectCentered" CanShrink="True" />
|
||||
<Control SetSize="48 48" Access="Public" Name="CollapseButtonWrapper">
|
||||
<Button Name="CollapseButton" Access="Public" Text="▶"
|
||||
ToggleMode="True" StyleClasses="ButtonSquare" SetSize="48 48" />
|
||||
</Control>
|
||||
<Label Name="Label" Access="Public"
|
||||
VAlign="Center"
|
||||
VerticalExpand="True"
|
||||
MinHeight="48"
|
||||
Margin="5 0"
|
||||
HorizontalExpand="True" ClipText="True" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<BoxContainer Name="ChildrenPrototypes" Access="Public" Orientation="Vertical"
|
||||
Margin="24 0 0 0" />
|
||||
</BoxContainer>
|
||||
</mapping:MappingSpawnButton>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MappingSpawnButton : Control
|
||||
{
|
||||
public MappingPrototype? Prototype;
|
||||
|
||||
public MappingSpawnButton()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,936 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.ContextMenu.UI;
|
||||
using Content.Client.Decals;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static System.StringComparison;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
using static Robust.Client.UserInterface.Controls.OptionButton;
|
||||
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
||||
|
||||
namespace Content.Client.Mapping;
|
||||
|
||||
public sealed class MappingState : GameplayStateBase
|
||||
{
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entityNetwork = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||
[Dependency] private readonly MappingManager _mapping = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlays = default!;
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resources = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private EntityMenuUIController _entityMenuController = default!;
|
||||
|
||||
private DecalPlacementSystem _decal = default!;
|
||||
private SpriteSystem _sprite = default!;
|
||||
private TransformSystem _transform = default!;
|
||||
private VerbSystem _verbs = default!;
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly GameplayStateLoadController _loadController;
|
||||
private bool _setup;
|
||||
private readonly List<MappingPrototype> _allPrototypes = new();
|
||||
private readonly Dictionary<IPrototype, MappingPrototype> _allPrototypesDict = new();
|
||||
private readonly Dictionary<Type, Dictionary<string, MappingPrototype>> _idDict = new();
|
||||
private readonly List<MappingPrototype> _prototypes = new();
|
||||
private (TimeSpan At, MappingSpawnButton Button)? _lastClicked;
|
||||
private Control? _scrollTo;
|
||||
private bool _updatePlacement;
|
||||
private bool _updateEraseDecal;
|
||||
|
||||
private MappingScreen Screen => (MappingScreen) UserInterfaceManager.ActiveScreen!;
|
||||
private MainViewport Viewport => UserInterfaceManager.ActiveScreen!.GetWidget<MainViewport>()!;
|
||||
|
||||
public CursorState State { get; set; }
|
||||
|
||||
public MappingState()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_sawmill = _log.GetSawmill("mapping");
|
||||
_loadController = UserInterfaceManager.GetUIController<GameplayStateLoadController>();
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
EnsureSetup();
|
||||
base.Startup();
|
||||
|
||||
UserInterfaceManager.LoadScreen<MappingScreen>();
|
||||
_loadController.LoadScreen();
|
||||
|
||||
var context = _input.Contexts.GetContext("common");
|
||||
context.AddFunction(ContentKeyFunctions.MappingUnselect);
|
||||
context.AddFunction(ContentKeyFunctions.SaveMap);
|
||||
context.AddFunction(ContentKeyFunctions.MappingEnablePick);
|
||||
context.AddFunction(ContentKeyFunctions.MappingEnableDelete);
|
||||
context.AddFunction(ContentKeyFunctions.MappingPick);
|
||||
context.AddFunction(ContentKeyFunctions.MappingRemoveDecal);
|
||||
context.AddFunction(ContentKeyFunctions.MappingCancelEraseDecal);
|
||||
context.AddFunction(ContentKeyFunctions.MappingOpenContextMenu);
|
||||
|
||||
Screen.DecalSystem = _decal;
|
||||
Screen.Prototypes.SearchBar.OnTextChanged += OnSearch;
|
||||
Screen.Prototypes.CollapseAllButton.OnPressed += OnCollapseAll;
|
||||
Screen.Prototypes.ClearSearchButton.OnPressed += OnClearSearch;
|
||||
Screen.Prototypes.GetPrototypeData += OnGetData;
|
||||
Screen.Prototypes.SelectionChanged += OnSelected;
|
||||
Screen.Prototypes.CollapseToggled += OnCollapseToggled;
|
||||
Screen.Pick.OnPressed += OnPickPressed;
|
||||
Screen.Delete.OnPressed += OnDeletePressed;
|
||||
Screen.EntityReplaceButton.OnToggled += OnEntityReplacePressed;
|
||||
Screen.EntityPlacementMode.OnItemSelected += OnEntityPlacementSelected;
|
||||
Screen.EraseEntityButton.OnToggled += OnEraseEntityPressed;
|
||||
Screen.EraseDecalButton.OnToggled += OnEraseDecalPressed;
|
||||
_placement.PlacementChanged += OnPlacementChanged;
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.MappingUnselect, new PointerInputCmdHandler(HandleMappingUnselect, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.SaveMap, new PointerInputCmdHandler(HandleSaveMap, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingEnablePick, new PointerStateInputCmdHandler(HandleEnablePick, HandleDisablePick, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingEnableDelete, new PointerStateInputCmdHandler(HandleEnableDelete, HandleDisableDelete, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingPick, new PointerInputCmdHandler(HandlePick, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingRemoveDecal, new PointerInputCmdHandler(HandleEditorCancelPlace, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingCancelEraseDecal, new PointerInputCmdHandler(HandleCancelEraseDecal, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.MappingOpenContextMenu, new PointerInputCmdHandler(HandleOpenContextMenu, outsidePrediction: true))
|
||||
.Register<MappingState>();
|
||||
|
||||
_overlays.AddOverlay(new MappingOverlay(this));
|
||||
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypesReloaded;
|
||||
|
||||
Screen.Prototypes.UpdateVisible(_prototypes);
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.WasModified<EntityPrototype>() &&
|
||||
!obj.WasModified<ContentTileDefinition>() &&
|
||||
!obj.WasModified<DecalPrototype>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReloadPrototypes();
|
||||
}
|
||||
|
||||
private bool HandleOpenContextMenu(in PointerInputCmdArgs args)
|
||||
{
|
||||
Deselect();
|
||||
|
||||
var coords = args.Coordinates.ToMap(_entityManager, _transform);
|
||||
if (_verbs.TryGetEntityMenuEntities(coords, out var entities))
|
||||
_entityMenuController.OpenRootMenu(entities);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<MappingState>();
|
||||
|
||||
Screen.Prototypes.SearchBar.OnTextChanged -= OnSearch;
|
||||
Screen.Prototypes.CollapseAllButton.OnPressed -= OnCollapseAll;
|
||||
Screen.Prototypes.ClearSearchButton.OnPressed -= OnClearSearch;
|
||||
Screen.Prototypes.GetPrototypeData -= OnGetData;
|
||||
Screen.Prototypes.SelectionChanged -= OnSelected;
|
||||
Screen.Prototypes.CollapseToggled -= OnCollapseToggled;
|
||||
Screen.Pick.OnPressed -= OnPickPressed;
|
||||
Screen.Delete.OnPressed -= OnDeletePressed;
|
||||
Screen.EntityReplaceButton.OnToggled -= OnEntityReplacePressed;
|
||||
Screen.EntityPlacementMode.OnItemSelected -= OnEntityPlacementSelected;
|
||||
Screen.EraseEntityButton.OnToggled -= OnEraseEntityPressed;
|
||||
Screen.EraseDecalButton.OnToggled -= OnEraseDecalPressed;
|
||||
_placement.PlacementChanged -= OnPlacementChanged;
|
||||
_prototypeManager.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
|
||||
UserInterfaceManager.ClearWindows();
|
||||
_loadController.UnloadScreen();
|
||||
UserInterfaceManager.UnloadScreen();
|
||||
|
||||
var context = _input.Contexts.GetContext("common");
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingUnselect);
|
||||
context.RemoveFunction(ContentKeyFunctions.SaveMap);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingEnablePick);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingEnableDelete);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingPick);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingRemoveDecal);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingCancelEraseDecal);
|
||||
context.RemoveFunction(ContentKeyFunctions.MappingOpenContextMenu);
|
||||
|
||||
_overlays.RemoveOverlay<MappingOverlay>();
|
||||
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void EnsureSetup()
|
||||
{
|
||||
if (_setup)
|
||||
return;
|
||||
|
||||
_setup = true;
|
||||
|
||||
_entityMenuController = UserInterfaceManager.GetUIController<EntityMenuUIController>();
|
||||
|
||||
_decal = _entityManager.System<DecalPlacementSystem>();
|
||||
_sprite = _entityManager.System<SpriteSystem>();
|
||||
_transform = _entityManager.System<TransformSystem>();
|
||||
_verbs = _entityManager.System<VerbSystem>();
|
||||
ReloadPrototypes();
|
||||
}
|
||||
|
||||
private void ReloadPrototypes()
|
||||
{
|
||||
var entities = new MappingPrototype(null, Loc.GetString("mapping-entities")) { Children = new List<MappingPrototype>() };
|
||||
_prototypes.Add(entities);
|
||||
|
||||
var mappings = new Dictionary<string, MappingPrototype>();
|
||||
foreach (var entity in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
Register(entity, entity.ID, entities);
|
||||
}
|
||||
|
||||
Sort(mappings, entities);
|
||||
mappings.Clear();
|
||||
|
||||
var tiles = new MappingPrototype(null, Loc.GetString("mapping-tiles")) { Children = new List<MappingPrototype>() };
|
||||
_prototypes.Add(tiles);
|
||||
|
||||
foreach (var tile in _prototypeManager.EnumeratePrototypes<ContentTileDefinition>())
|
||||
{
|
||||
Register(tile, tile.ID, tiles);
|
||||
}
|
||||
|
||||
Sort(mappings, tiles);
|
||||
mappings.Clear();
|
||||
|
||||
var decals = new MappingPrototype(null, Loc.GetString("mapping-decals")) { Children = new List<MappingPrototype>() };
|
||||
_prototypes.Add(decals);
|
||||
|
||||
foreach (var decal in _prototypeManager.EnumeratePrototypes<DecalPrototype>())
|
||||
{
|
||||
Register(decal, decal.ID, decals);
|
||||
}
|
||||
|
||||
Sort(mappings, decals);
|
||||
mappings.Clear();
|
||||
}
|
||||
|
||||
private void Sort(Dictionary<string, MappingPrototype> prototypes, MappingPrototype topLevel)
|
||||
{
|
||||
static int Compare(MappingPrototype a, MappingPrototype b)
|
||||
{
|
||||
return string.Compare(a.Name, b.Name, OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
topLevel.Children ??= new List<MappingPrototype>();
|
||||
|
||||
foreach (var prototype in prototypes.Values)
|
||||
{
|
||||
if (prototype.Parents == null && prototype != topLevel)
|
||||
{
|
||||
prototype.Parents = new List<MappingPrototype> { topLevel };
|
||||
topLevel.Children.Add(prototype);
|
||||
}
|
||||
|
||||
prototype.Parents?.Sort(Compare);
|
||||
prototype.Children?.Sort(Compare);
|
||||
}
|
||||
|
||||
topLevel.Children.Sort(Compare);
|
||||
}
|
||||
|
||||
private MappingPrototype? Register<T>(T? prototype, string id, MappingPrototype topLevel) where T : class, IPrototype, IInheritingPrototype
|
||||
{
|
||||
{
|
||||
if (prototype == null &&
|
||||
_prototypeManager.TryIndex(id, out prototype) &&
|
||||
prototype is EntityPrototype entity)
|
||||
{
|
||||
if (entity.HideSpawnMenu || entity.Abstract)
|
||||
prototype = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (prototype == null)
|
||||
{
|
||||
if (!_prototypeManager.TryGetMapping(typeof(T), id, out var node))
|
||||
{
|
||||
_sawmill.Error($"No {nameof(T)} found with id {id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var ids = _idDict.GetOrNew(typeof(T));
|
||||
if (ids.TryGetValue(id, out var mapping))
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = node.TryGet("name", out ValueDataNode? nameNode)
|
||||
? nameNode.Value
|
||||
: id;
|
||||
|
||||
if (node.TryGet("suffix", out ValueDataNode? suffix))
|
||||
name = $"{name} [{suffix.Value}]";
|
||||
|
||||
mapping = new MappingPrototype(prototype, name);
|
||||
_allPrototypes.Add(mapping);
|
||||
ids.Add(id, mapping);
|
||||
|
||||
if (node.TryGet("parent", out ValueDataNode? parentValue))
|
||||
{
|
||||
var parent = Register<T>(null, parentValue.Value, topLevel);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(parent);
|
||||
parent.Children ??= new List<MappingPrototype>();
|
||||
parent.Children.Add(mapping);
|
||||
}
|
||||
}
|
||||
else if (node.TryGet("parent", out SequenceDataNode? parentSequence))
|
||||
{
|
||||
foreach (var parentNode in parentSequence.Cast<ValueDataNode>())
|
||||
{
|
||||
var parent = Register<T>(null, parentNode.Value, topLevel);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(parent);
|
||||
parent.Children ??= new List<MappingPrototype>();
|
||||
parent.Children.Add(mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
topLevel.Children ??= new List<MappingPrototype>();
|
||||
topLevel.Children.Add(mapping);
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(topLevel);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var ids = _idDict.GetOrNew(typeof(T));
|
||||
if (ids.TryGetValue(id, out var mapping))
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
var entity = prototype as EntityPrototype;
|
||||
var name = entity?.Name ?? prototype.ID;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entity?.EditorSuffix))
|
||||
name = $"{name} [{entity.EditorSuffix}]";
|
||||
|
||||
mapping = new MappingPrototype(prototype, name);
|
||||
_allPrototypes.Add(mapping);
|
||||
_allPrototypesDict.Add(prototype, mapping);
|
||||
ids.Add(prototype.ID, mapping);
|
||||
}
|
||||
|
||||
if (prototype.Parents == null)
|
||||
{
|
||||
topLevel.Children ??= new List<MappingPrototype>();
|
||||
topLevel.Children.Add(mapping);
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(topLevel);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
foreach (var parentId in prototype.Parents)
|
||||
{
|
||||
var parent = Register<T>(null, parentId, topLevel);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
mapping.Parents ??= new List<MappingPrototype>();
|
||||
mapping.Parents.Add(parent);
|
||||
parent.Children ??= new List<MappingPrototype>();
|
||||
parent.Children.Add(mapping);
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlacementChanged(object? sender, EventArgs e)
|
||||
{
|
||||
_updatePlacement = true;
|
||||
}
|
||||
|
||||
protected override void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args)
|
||||
{
|
||||
if (args.Viewport == null)
|
||||
base.OnKeyBindStateChanged(new ViewportBoundKeyEventArgs(args.KeyEventArgs, Viewport.Viewport));
|
||||
else
|
||||
base.OnKeyBindStateChanged(args);
|
||||
}
|
||||
|
||||
private void OnSearch(LineEditEventArgs args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(args.Text))
|
||||
{
|
||||
Screen.Prototypes.PrototypeList.Visible = true;
|
||||
Screen.Prototypes.SearchList.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var matches = new List<MappingPrototype>();
|
||||
foreach (var prototype in _allPrototypes)
|
||||
{
|
||||
if (prototype.Name.Contains(args.Text, OrdinalIgnoreCase))
|
||||
matches.Add(prototype);
|
||||
}
|
||||
|
||||
matches.Sort(static (a, b) => string.Compare(a.Name, b.Name, OrdinalIgnoreCase));
|
||||
|
||||
Screen.Prototypes.PrototypeList.Visible = false;
|
||||
Screen.Prototypes.SearchList.Visible = true;
|
||||
Screen.Prototypes.Search(matches);
|
||||
}
|
||||
|
||||
private void OnCollapseAll(ButtonEventArgs args)
|
||||
{
|
||||
foreach (var child in Screen.Prototypes.PrototypeList.Children)
|
||||
{
|
||||
if (child is not MappingSpawnButton button)
|
||||
continue;
|
||||
|
||||
Collapse(button);
|
||||
}
|
||||
|
||||
Screen.Prototypes.ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
}
|
||||
|
||||
private void OnClearSearch(ButtonEventArgs obj)
|
||||
{
|
||||
Screen.Prototypes.SearchBar.Text = string.Empty;
|
||||
OnSearch(new LineEditEventArgs(Screen.Prototypes.SearchBar, string.Empty));
|
||||
}
|
||||
|
||||
private void OnGetData(IPrototype prototype, List<Texture> textures)
|
||||
{
|
||||
switch (prototype)
|
||||
{
|
||||
case EntityPrototype entity:
|
||||
textures.AddRange(SpriteComponent.GetPrototypeTextures(entity, _resources).Select(t => t.Default));
|
||||
break;
|
||||
case DecalPrototype decal:
|
||||
textures.Add(_sprite.Frame0(decal.Sprite));
|
||||
break;
|
||||
case ContentTileDefinition tile:
|
||||
if (tile.Sprite?.ToString() is { } sprite)
|
||||
textures.Add(_resources.GetResource<TextureResource>(sprite).Texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelected(MappingPrototype mapping)
|
||||
{
|
||||
if (mapping.Prototype == null)
|
||||
return;
|
||||
|
||||
var chain = new Stack<MappingPrototype>();
|
||||
chain.Push(mapping);
|
||||
|
||||
var parent = mapping.Parents?.FirstOrDefault();
|
||||
while (parent != null)
|
||||
{
|
||||
chain.Push(parent);
|
||||
parent = parent.Parents?.FirstOrDefault();
|
||||
}
|
||||
|
||||
_lastClicked = null;
|
||||
|
||||
Control? last = null;
|
||||
var children = Screen.Prototypes.PrototypeList.Children;
|
||||
foreach (var prototype in chain)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child is MappingSpawnButton button &&
|
||||
button.Prototype == prototype)
|
||||
{
|
||||
UnCollapse(button);
|
||||
OnSelected(button, prototype.Prototype);
|
||||
children = button.ChildrenPrototypes.Children;
|
||||
last = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (last != null && Screen.Prototypes.PrototypeList.Visible)
|
||||
_scrollTo = last;
|
||||
}
|
||||
|
||||
private void OnSelected(MappingSpawnButton button, IPrototype? prototype)
|
||||
{
|
||||
var time = _timing.CurTime;
|
||||
if (prototype is DecalPrototype)
|
||||
Screen.SelectDecal(prototype.ID);
|
||||
|
||||
// Double-click functionality if it's collapsible.
|
||||
if (_lastClicked is { } lastClicked &&
|
||||
lastClicked.Button == button &&
|
||||
lastClicked.At > time - TimeSpan.FromSeconds(0.333) &&
|
||||
string.IsNullOrEmpty(Screen.Prototypes.SearchBar.Text) &&
|
||||
button.CollapseButton.Visible)
|
||||
{
|
||||
button.CollapseButton.Pressed = !button.CollapseButton.Pressed;
|
||||
ToggleCollapse(button);
|
||||
button.Button.Pressed = true;
|
||||
Screen.Prototypes.Selected = button;
|
||||
_lastClicked = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle if it's the same button (at least if we just unclicked it).
|
||||
if (!button.Button.Pressed && button.Prototype?.Prototype != null && _lastClicked?.Button == button)
|
||||
{
|
||||
_lastClicked = null;
|
||||
Deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastClicked = (time, button);
|
||||
|
||||
if (button.Prototype == null)
|
||||
return;
|
||||
|
||||
if (Screen.Prototypes.Selected is { } oldButton &&
|
||||
oldButton != button)
|
||||
{
|
||||
Deselect();
|
||||
}
|
||||
|
||||
Screen.EntityContainer.Visible = false;
|
||||
Screen.DecalContainer.Visible = false;
|
||||
|
||||
switch (prototype)
|
||||
{
|
||||
case EntityPrototype entity:
|
||||
{
|
||||
var placementId = Screen.EntityPlacementMode.SelectedId;
|
||||
|
||||
var placement = new PlacementInformation
|
||||
{
|
||||
PlacementOption = placementId > 0 ? EntitySpawnWindow.InitOpts[placementId] : entity.PlacementMode,
|
||||
EntityType = entity.ID,
|
||||
IsTile = false
|
||||
};
|
||||
|
||||
Screen.EntityContainer.Visible = true;
|
||||
_decal.SetActive(false);
|
||||
_placement.BeginPlacing(placement);
|
||||
break;
|
||||
}
|
||||
case DecalPrototype decal:
|
||||
_placement.Clear();
|
||||
|
||||
_decal.SetActive(true);
|
||||
_decal.UpdateDecalInfo(decal.ID, Color.White, 0, true, 0, false);
|
||||
Screen.DecalContainer.Visible = true;
|
||||
break;
|
||||
case ContentTileDefinition tile:
|
||||
{
|
||||
var placement = new PlacementInformation
|
||||
{
|
||||
PlacementOption = "AlignTileAny",
|
||||
TileType = tile.TileId,
|
||||
IsTile = true
|
||||
};
|
||||
|
||||
_decal.SetActive(false);
|
||||
_placement.BeginPlacing(placement);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
_placement.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
Screen.Prototypes.Selected = button;
|
||||
|
||||
button.Button.Pressed = true;
|
||||
}
|
||||
|
||||
private void Deselect()
|
||||
{
|
||||
if (Screen.Prototypes.Selected is { } selected)
|
||||
{
|
||||
selected.Button.Pressed = false;
|
||||
Screen.Prototypes.Selected = null;
|
||||
|
||||
if (selected.Prototype?.Prototype is DecalPrototype)
|
||||
{
|
||||
_decal.SetActive(false);
|
||||
Screen.DecalContainer.Visible = false;
|
||||
}
|
||||
|
||||
if (selected.Prototype?.Prototype is EntityPrototype)
|
||||
{
|
||||
_placement.Clear();
|
||||
}
|
||||
|
||||
if (selected.Prototype?.Prototype is ContentTileDefinition)
|
||||
{
|
||||
_placement.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollapseToggled(MappingSpawnButton button, ButtonToggledEventArgs args)
|
||||
{
|
||||
ToggleCollapse(button);
|
||||
}
|
||||
|
||||
private void OnPickPressed(ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
EnablePick();
|
||||
else
|
||||
DisablePick();
|
||||
}
|
||||
|
||||
private void OnDeletePressed(ButtonEventArgs obj)
|
||||
{
|
||||
if (obj.Button.Pressed)
|
||||
EnableDelete();
|
||||
else
|
||||
DisableDelete();
|
||||
}
|
||||
|
||||
private void OnEntityReplacePressed(ButtonToggledEventArgs args)
|
||||
{
|
||||
_placement.Replacement = args.Pressed;
|
||||
}
|
||||
|
||||
private void OnEntityPlacementSelected(ItemSelectedEventArgs args)
|
||||
{
|
||||
Screen.EntityPlacementMode.SelectId(args.Id);
|
||||
|
||||
if (_placement.CurrentMode != null)
|
||||
{
|
||||
var placement = new PlacementInformation
|
||||
{
|
||||
PlacementOption = EntitySpawnWindow.InitOpts[args.Id],
|
||||
EntityType = _placement.CurrentPermission!.EntityType,
|
||||
TileType = _placement.CurrentPermission.TileType,
|
||||
Range = 2,
|
||||
IsTile = _placement.CurrentPermission.IsTile,
|
||||
};
|
||||
|
||||
_placement.BeginPlacing(placement);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEraseEntityPressed(ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button.Pressed == _placement.Eraser)
|
||||
return;
|
||||
|
||||
if (args.Button.Pressed)
|
||||
EnableEraser();
|
||||
else
|
||||
DisableEraser();
|
||||
}
|
||||
|
||||
private void OnEraseDecalPressed(ButtonToggledEventArgs args)
|
||||
{
|
||||
_placement.Clear();
|
||||
Deselect();
|
||||
Screen.EraseEntityButton.Pressed = false;
|
||||
_updatePlacement = true;
|
||||
_updateEraseDecal = args.Pressed;
|
||||
}
|
||||
|
||||
private void EnableEraser()
|
||||
{
|
||||
if (_placement.Eraser)
|
||||
return;
|
||||
|
||||
_placement.Clear();
|
||||
_placement.ToggleEraser();
|
||||
Screen.EntityPlacementMode.Disabled = true;
|
||||
Screen.EraseDecalButton.Pressed = false;
|
||||
Deselect();
|
||||
}
|
||||
|
||||
private void DisableEraser()
|
||||
{
|
||||
if (!_placement.Eraser)
|
||||
return;
|
||||
|
||||
_placement.ToggleEraser();
|
||||
Screen.EntityPlacementMode.Disabled = false;
|
||||
}
|
||||
|
||||
private void EnablePick()
|
||||
{
|
||||
Screen.UnPressActionsExcept(Screen.Pick);
|
||||
State = CursorState.Pick;
|
||||
}
|
||||
|
||||
private void DisablePick()
|
||||
{
|
||||
Screen.Pick.Pressed = false;
|
||||
State = CursorState.None;
|
||||
}
|
||||
|
||||
private void EnableDelete()
|
||||
{
|
||||
Screen.UnPressActionsExcept(Screen.Delete);
|
||||
State = CursorState.Delete;
|
||||
EnableEraser();
|
||||
}
|
||||
|
||||
private void DisableDelete()
|
||||
{
|
||||
Screen.Delete.Pressed = false;
|
||||
State = CursorState.None;
|
||||
DisableEraser();
|
||||
}
|
||||
|
||||
private bool HandleMappingUnselect(in PointerInputCmdArgs args)
|
||||
{
|
||||
if (Screen.Prototypes.Selected is not { Prototype.Prototype: DecalPrototype })
|
||||
return false;
|
||||
|
||||
Deselect();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleSaveMap(in PointerInputCmdArgs args)
|
||||
{
|
||||
#if FULL_RELEASE
|
||||
return false;
|
||||
#endif
|
||||
if (!_admin.IsAdmin(true) || !_admin.HasFlag(AdminFlags.Host))
|
||||
return false;
|
||||
|
||||
SaveMap();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleEnablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
EnablePick();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleDisablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
DisablePick();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleEnableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
EnableDelete();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleDisableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
DisableDelete();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandlePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (State != CursorState.Pick)
|
||||
return false;
|
||||
|
||||
MappingPrototype? button = null;
|
||||
|
||||
// Try and get tile under it
|
||||
// TODO: Separate mode for decals.
|
||||
if (!uid.IsValid())
|
||||
{
|
||||
var mapPos = _transform.ToMapCoordinates(coords);
|
||||
|
||||
if (_mapMan.TryFindGridAt(mapPos, out var gridUid, out var grid) &&
|
||||
_entityManager.System<SharedMapSystem>().TryGetTileRef(gridUid, grid, coords, out var tileRef) &&
|
||||
_allPrototypesDict.TryGetValue(tileRef.GetContentTileDefinition(), out button))
|
||||
{
|
||||
OnSelected(button);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (button == null)
|
||||
{
|
||||
if (uid == EntityUid.Invalid ||
|
||||
_entityManager.GetComponentOrNull<MetaDataComponent>(uid) is not { EntityPrototype: { } prototype } ||
|
||||
!_allPrototypesDict.TryGetValue(prototype, out button))
|
||||
{
|
||||
// we always block other input handlers if pick mode is enabled
|
||||
// this makes you not accidentally place something in space because you
|
||||
// miss-clicked while holding down the pick hotkey
|
||||
return true;
|
||||
}
|
||||
|
||||
// Selected an entity
|
||||
OnSelected(button);
|
||||
|
||||
// Match rotation
|
||||
_placement.Direction = _entityManager.GetComponent<TransformComponent>(uid).LocalRotation.GetDir();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleEditorCancelPlace(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (!Screen.EraseDecalButton.Pressed)
|
||||
return false;
|
||||
|
||||
_entityNetwork.SendSystemNetworkMessage(new RequestDecalRemovalEvent(_entityManager.GetNetCoordinates(coords)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleCancelEraseDecal(in PointerInputCmdArgs args)
|
||||
{
|
||||
if (!Screen.EraseDecalButton.Pressed)
|
||||
return false;
|
||||
|
||||
Screen.EraseDecalButton.Pressed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void SaveMap()
|
||||
{
|
||||
await _mapping.SaveMap();
|
||||
}
|
||||
|
||||
private void ToggleCollapse(MappingSpawnButton button)
|
||||
{
|
||||
if (button.CollapseButton.Pressed)
|
||||
{
|
||||
if (button.Prototype?.Children != null)
|
||||
{
|
||||
foreach (var child in button.Prototype.Children)
|
||||
{
|
||||
Screen.Prototypes.Insert(button.ChildrenPrototypes, child, true);
|
||||
}
|
||||
}
|
||||
|
||||
button.CollapseButton.Label.Text = "▼";
|
||||
}
|
||||
else
|
||||
{
|
||||
button.ChildrenPrototypes.DisposeAllChildren();
|
||||
button.CollapseButton.Label.Text = "▶";
|
||||
}
|
||||
}
|
||||
|
||||
private void Collapse(MappingSpawnButton button)
|
||||
{
|
||||
if (!button.CollapseButton.Pressed)
|
||||
return;
|
||||
|
||||
button.CollapseButton.Pressed = false;
|
||||
ToggleCollapse(button);
|
||||
}
|
||||
|
||||
|
||||
private void UnCollapse(MappingSpawnButton button)
|
||||
{
|
||||
if (button.CollapseButton.Pressed)
|
||||
return;
|
||||
|
||||
button.CollapseButton.Pressed = true;
|
||||
ToggleCollapse(button);
|
||||
}
|
||||
|
||||
public EntityUid? GetHoveredEntity()
|
||||
{
|
||||
if (UserInterfaceManager.CurrentlyHovered is not IViewportControl viewport ||
|
||||
_input.MouseScreenPosition is not { IsValid: true } position)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var mapPos = viewport.PixelToMap(position.Position);
|
||||
return GetClickedEntity(mapPos);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs e)
|
||||
{
|
||||
if (_updatePlacement)
|
||||
{
|
||||
_updatePlacement = false;
|
||||
|
||||
if (!_placement.IsActive && _decal.GetActiveDecal().Decal == null)
|
||||
Deselect();
|
||||
|
||||
Screen.EraseEntityButton.Pressed = _placement.Eraser;
|
||||
Screen.EraseDecalButton.Pressed = _updateEraseDecal;
|
||||
Screen.EntityPlacementMode.Disabled = _placement.Eraser;
|
||||
}
|
||||
|
||||
if (_scrollTo is not { } scrollTo)
|
||||
return;
|
||||
|
||||
// this is not ideal but we wait until the control's height is computed to use
|
||||
// its position to scroll to
|
||||
if (scrollTo.Height > 0 && Screen.Prototypes.PrototypeList.Visible)
|
||||
{
|
||||
var y = scrollTo.GlobalPosition.Y - Screen.Prototypes.ScrollContainer.Height / 2 + scrollTo.Height;
|
||||
var scroll = Screen.Prototypes.ScrollContainer;
|
||||
scroll.SetScrollValue(scroll.GetScrollValue() + new Vector2(0, y));
|
||||
_scrollTo = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO this doesn't handle pressing down multiple state hotkeys at the moment
|
||||
public enum CursorState
|
||||
{
|
||||
None,
|
||||
Pick,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ public sealed partial class MappingSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly IPlacementManager _placementMan = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -26,8 +25,6 @@ public sealed partial class MappingSystem : EntitySystem
|
|||
/// </summary>
|
||||
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
|
||||
|
||||
public string DefaultMappingActions = "/mapping_actions.yml";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -36,11 +33,6 @@ public sealed partial class MappingSystem : EntitySystem
|
|||
SubscribeLocalEvent<StartPlacementActionEvent>(OnStartPlacementAction);
|
||||
}
|
||||
|
||||
public void LoadMappingActions()
|
||||
{
|
||||
_actionsSystem.LoadActionAssignments(DefaultMappingActions, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This checks if the placement manager is currently active, and attempts to copy the placement information for
|
||||
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
|
||||
PlaceHolder="{Loc crew-monitor-filter-line-placeholder}" />
|
||||
|
||||
<ScrollContainer Name="SensorScroller"
|
||||
VerticalExpand="True"
|
||||
SetWidth="520"
|
||||
|
|
|
|||
|
|
@ -156,6 +156,11 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||
// Populate departments
|
||||
foreach (var sensor in departmentSensors)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SearchLineEdit.Text)
|
||||
&& !sensor.Name.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase)
|
||||
&& !sensor.Job.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||
|
||||
// Add a button that will hold a username and other details
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<Control xmlns="https://spacestation14.io" HorizontalExpand="True">
|
||||
<BoxContainer Name="MainContainer"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer Name="ColorPanel"
|
||||
VerticalExpand="True"
|
||||
SetWidth="7"
|
||||
Margin="0 1 0 0" />
|
||||
<Button Name="MainButton"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="ButtonSquare"
|
||||
Margin="-1 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="BeaconNameLabel" />
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public readonly EntityCoordinates BeaconPosition;
|
||||
public Action<EntityCoordinates>? OnPressed;
|
||||
public string? Label => BeaconNameLabel.Text;
|
||||
private StyleBoxFlat _styleBox;
|
||||
public Color Color => _styleBox.BackgroundColor;
|
||||
|
||||
public StationMapBeaconControl(EntityUid mapUid, SharedNavMapSystem.NavMapBeacon beacon)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
BeaconPosition = new EntityCoordinates(mapUid, beacon.Position);
|
||||
|
||||
_styleBox = new StyleBoxFlat { BackgroundColor = beacon.Color };
|
||||
ColorPanel.PanelOverride = _styleBox;
|
||||
BeaconNameLabel.Text = beacon.Text;
|
||||
|
||||
MainButton.OnPressed += args => OnPressed?.Invoke(BeaconPosition);
|
||||
}
|
||||
|
||||
public int CompareTo(StationMapBeaconControl? other)
|
||||
{
|
||||
if (other == null)
|
||||
return 1;
|
||||
|
||||
// Group by color
|
||||
var colorCompare = Color.ToArgb().CompareTo(other.Color.ToArgb());
|
||||
if (colorCompare != 0)
|
||||
{
|
||||
return colorCompare;
|
||||
}
|
||||
|
||||
// If same color, sort by text
|
||||
return string.Compare(Label, other.Label);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,9 +24,16 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
|
|||
|
||||
_window = this.CreateWindow<StationMapWindow>();
|
||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
string stationName = string.Empty;
|
||||
if(EntMan.TryGetComponent<MetaDataComponent>(gridUid, out var gridMetaData))
|
||||
{
|
||||
stationName = gridMetaData.EntityName;
|
||||
}
|
||||
|
||||
if (EntMan.TryGetComponent<StationMapComponent>(Owner, out var comp) && comp.ShowLocation)
|
||||
_window.Set(gridUid, Owner);
|
||||
_window.Set(stationName, gridUid, Owner);
|
||||
else
|
||||
_window.Set(gridUid, null);
|
||||
_window.Set(stationName, gridUid, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,28 @@
|
|||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
Title="{Loc 'station-map-window-title'}"
|
||||
Resizable="False"
|
||||
SetSize="668 713"
|
||||
MinSize="668 713">
|
||||
SetSize="868 748"
|
||||
MinSize="868 748">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 8 0 10" VerticalAlignment="Top">
|
||||
<!-- Station name -->
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<Label Name="StationName" Text="Unknown station" StyleClasses="LabelBig" Align="Center"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalAlignment="Top">
|
||||
<ui:NavMapControl Name="NavMapScreen"/>
|
||||
|
||||
<BoxContainer Orientation="Vertical" SetWidth="200">
|
||||
<!-- Search bar -->
|
||||
<LineEdit Name="FilterBar" PlaceHolder="{Loc 'station-map-filter-placeholder'}" Margin="0 0 10 10" HorizontalExpand="True"/>
|
||||
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<!-- Beacon Buttons (filled by code) -->
|
||||
<BoxContainer Name="BeaconButtons" Orientation="Vertical" HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
|
|
|
|||
|
|
@ -3,24 +3,75 @@ using Content.Client.UserInterface.Controls;
|
|||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared.Pinpointer;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly List<StationMapBeaconControl> _buttons = new();
|
||||
|
||||
public StationMapWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
FilterBar.OnTextChanged += (bar) => OnFilterChanged(bar.Text);
|
||||
}
|
||||
|
||||
public void Set(EntityUid? mapUid, EntityUid? trackedEntity)
|
||||
public void Set(string stationName, EntityUid? mapUid, EntityUid? trackedEntity)
|
||||
{
|
||||
NavMapScreen.MapUid = mapUid;
|
||||
|
||||
if (trackedEntity != null)
|
||||
NavMapScreen.TrackedCoordinates.Add(new EntityCoordinates(trackedEntity.Value, Vector2.Zero), (true, Color.Cyan));
|
||||
|
||||
if (!string.IsNullOrEmpty(stationName))
|
||||
{
|
||||
StationName.Text = stationName;
|
||||
}
|
||||
|
||||
NavMapScreen.ForceNavMapUpdate();
|
||||
UpdateBeaconList(mapUid);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFilterChanged(string newFilter)
|
||||
{
|
||||
foreach (var button in _buttons)
|
||||
{
|
||||
button.Visible = string.IsNullOrEmpty(newFilter) || (
|
||||
!string.IsNullOrEmpty(button.Label) &&
|
||||
button.Label.Contains(newFilter, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateBeaconList(EntityUid? mapUid)
|
||||
{
|
||||
BeaconButtons.Children.Clear();
|
||||
_buttons.Clear();
|
||||
|
||||
if (!mapUid.HasValue)
|
||||
return;
|
||||
|
||||
if (!_entMan.TryGetComponent<NavMapComponent>(mapUid, out var navMap))
|
||||
return;
|
||||
|
||||
foreach (var beacon in navMap.Beacons.Values)
|
||||
{
|
||||
var button = new StationMapBeaconControl(mapUid.Value, beacon);
|
||||
|
||||
button.OnPressed += NavMapScreen.CenterToCoordinates;
|
||||
|
||||
_buttons.Add(button);
|
||||
}
|
||||
|
||||
_buttons.Sort();
|
||||
|
||||
foreach (var button in _buttons)
|
||||
BeaconButtons.AddChild(button);
|
||||
}
|
||||
}
|
||||
|
|
@ -199,7 +199,9 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
|
||||
var gridMatrix = _transform.GetWorldMatrix(gUid);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
|
||||
var color = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
|
||||
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f);
|
||||
|
||||
// Others default:
|
||||
// Color.FromHex("#FFC000FF")
|
||||
|
|
@ -213,25 +215,52 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
|
||||
var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
|
||||
gridCentre.Y = -gridCentre.Y;
|
||||
|
||||
var distance = gridCentre.Length();
|
||||
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
|
||||
("distance", $"{distance:0.0}"));
|
||||
|
||||
var mapCoords = _transform.GetWorldPosition(gUid);
|
||||
var coordsText = $"({mapCoords.X:0.0}, {mapCoords.Y:0.0})";
|
||||
|
||||
// yes 1.0 scale is intended here.
|
||||
var labelDimensions = handle.GetDimensions(Font, labelText, 1f);
|
||||
var coordsDimensions = handle.GetDimensions(Font, coordsText, 0.7f);
|
||||
|
||||
// y-offset the control to always render below the grid (vertically)
|
||||
var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f;
|
||||
|
||||
// The actual position in the UI. We offset the matrix position to render it off by half its width
|
||||
// plus by the offset.
|
||||
var uiPosition = ScalePosition(gridCentre)- new Vector2(labelDimensions.X / 2f, -yOffset);
|
||||
// The actual position in the UI. We centre the label by offsetting the matrix position
|
||||
// by half the label's width, plus the y-offset
|
||||
var gridScaledPosition = ScalePosition(gridCentre) - new Vector2(0, -yOffset);
|
||||
|
||||
// Look this is uggo so feel free to cleanup. We just need to clamp the UI position to within the viewport.
|
||||
uiPosition = new Vector2(Math.Clamp(uiPosition.X, 0f, PixelWidth - labelDimensions.X ),
|
||||
Math.Clamp(uiPosition.Y, 0f, PixelHeight - labelDimensions.Y));
|
||||
// Normalize the grid position if it exceeds the viewport bounds
|
||||
// normalizing it instead of clamping it preserves the direction of the vector and prevents corner-hugging
|
||||
var gridOffset = gridScaledPosition / PixelSize - new Vector2(0.5f, 0.5f);
|
||||
var offsetMax = Math.Max(Math.Abs(gridOffset.X), Math.Abs(gridOffset.Y)) * 2f;
|
||||
if (offsetMax > 1)
|
||||
{
|
||||
gridOffset = new Vector2(gridOffset.X / offsetMax, gridOffset.Y / offsetMax);
|
||||
|
||||
handle.DrawString(Font, uiPosition, labelText, color);
|
||||
gridScaledPosition = (gridOffset + new Vector2(0.5f, 0.5f)) * PixelSize;
|
||||
}
|
||||
|
||||
var labelUiPosition = gridScaledPosition - new Vector2(labelDimensions.X / 2f, 0);
|
||||
var coordUiPosition = gridScaledPosition - new Vector2(coordsDimensions.X / 2f, -labelDimensions.Y);
|
||||
|
||||
// clamp the IFF label's UI position to within the viewport extents so it hugs the edges of the viewport
|
||||
// coord label intentionally isn't clamped so we don't get ugly clutter at the edges
|
||||
var controlExtents = PixelSize - new Vector2(labelDimensions.X, labelDimensions.Y); //new Vector2(labelDimensions.X * 2f, labelDimensions.Y);
|
||||
labelUiPosition = Vector2.Clamp(labelUiPosition, Vector2.Zero, controlExtents);
|
||||
|
||||
// draw IFF label
|
||||
handle.DrawString(Font, labelUiPosition, labelText, labelColor);
|
||||
|
||||
// only draw coords label if close enough
|
||||
if (offsetMax < 1)
|
||||
{
|
||||
handle.DrawString(Font, coordUiPosition, coordsText, 0.7f, coordColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Detailed view
|
||||
|
|
@ -241,7 +270,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
if (!gridAABB.Intersects(viewAABB))
|
||||
continue;
|
||||
|
||||
DrawGrid(handle, matty, grid, color);
|
||||
DrawGrid(handle, matty, grid, labelColor);
|
||||
DrawDocks(handle, gUid, matty);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -724,6 +724,18 @@ namespace Content.Client.Stylesheets
|
|||
new StyleProperty("font-color", Color.FromHex("#E5E5E581")),
|
||||
}),
|
||||
|
||||
// ItemStatus for hands
|
||||
Element()
|
||||
.Class(StyleClassItemStatusNotHeld)
|
||||
.Prop("font", notoSansItalic10)
|
||||
.Prop("font-color", ItemStatusNotHeldColor)
|
||||
.Prop(nameof(Control.Margin), new Thickness(4, 0, 0, 2)),
|
||||
|
||||
Element()
|
||||
.Class(StyleClassItemStatus)
|
||||
.Prop(nameof(RichTextLabel.LineHeightScale), 0.7f)
|
||||
.Prop(nameof(Control.Margin), new Thickness(4, 0, 0, 2)),
|
||||
|
||||
// Context Menu window
|
||||
Element<PanelContainer>().Class(ContextMenuPopup.StyleClassContextMenuPopup)
|
||||
.Prop(PanelContainer.StylePropertyPanel, contextMenuBackground),
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public sealed class ParacusiaSystem : SharedParacusiaSystem
|
|||
var newCoords = Transform(uid).Coordinates.Offset(randomOffset);
|
||||
|
||||
// Play the sound
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords).Value.Entity;
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords)?.Entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ public sealed partial class DialogWindow : FancyWindow
|
|||
Prompts.AddChild(box);
|
||||
}
|
||||
|
||||
// Grab keyboard focus for the first dialog entry
|
||||
_promptLines[0].Item2.GrabKeyboardFocus();
|
||||
|
||||
OkButton.OnPressed += _ => Confirm();
|
||||
|
||||
CancelButton.OnPressed += _ =>
|
||||
|
|
|
|||
|
|
@ -774,7 +774,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||
|
||||
private void LoadGui()
|
||||
{
|
||||
DebugTools.Assert(_window == null);
|
||||
UnloadGui();
|
||||
_window = UIManager.CreateWindow<ActionsWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Numerics;
|
|||
using Content.Client.CombatMode;
|
||||
using Content.Client.ContextMenu.UI;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.Player;
|
||||
|
|
@ -22,7 +23,9 @@ namespace Content.Client.Verbs.UI
|
|||
/// open a verb menu for a given entity, add verbs to it, and add server-verbs when the server response is
|
||||
/// received.
|
||||
/// </remarks>
|
||||
public sealed class VerbMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>
|
||||
public sealed class VerbMenuUIController : UIController,
|
||||
IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
|
||||
IOnStateEntered<MappingState>, IOnStateExited<MappingState>
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly ContextMenuUIController _context = default!;
|
||||
|
|
@ -44,7 +47,6 @@ namespace Content.Client.Verbs.UI
|
|||
{
|
||||
_context.OnContextKeyEvent += OnKeyBindDown;
|
||||
_context.OnContextClosed += Close;
|
||||
_verbSystem.OnVerbsResponse += HandleVerbsResponse;
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
|
|
@ -56,6 +58,17 @@ namespace Content.Client.Verbs.UI
|
|||
Close();
|
||||
}
|
||||
|
||||
public void OnStateEntered(MappingState state)
|
||||
{
|
||||
_verbSystem.OnVerbsResponse += HandleVerbsResponse;
|
||||
}
|
||||
|
||||
public void OnStateExited(MappingState state)
|
||||
{
|
||||
if (_verbSystem != null)
|
||||
_verbSystem.OnVerbsResponse -= HandleVerbsResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a verb menu and fill it with verbs applicable to the given target entity.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -47,10 +47,11 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
|||
if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null)
|
||||
return;
|
||||
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true).Value.Entity;
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
|
||||
|
||||
if (!TryComp(weather.Stream, out AudioComponent? comp))
|
||||
return;
|
||||
|
||||
var stream = weather.Stream.Value;
|
||||
var comp = Comp<AudioComponent>(stream);
|
||||
var occlusion = 0f;
|
||||
|
||||
// Work out tiles nearby to determine volume.
|
||||
|
|
@ -115,7 +116,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
|||
|
||||
var alpha = GetPercent(weather, uid);
|
||||
alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume);
|
||||
_audio.SetGain(stream, alpha, comp);
|
||||
_audio.SetGain(weather.Stream, alpha, comp);
|
||||
comp.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -584,17 +584,10 @@ namespace Content.Client.Wires.UI
|
|||
|
||||
private sealed class HelpPopup : Popup
|
||||
{
|
||||
private const string Text = "Click on the gold contacts with a multitool in hand to pulse their wire.\n" +
|
||||
"Click on the wires with a pair of wirecutters in hand to cut/mend them.\n\n" +
|
||||
"The lights at the top show the state of the machine, " +
|
||||
"messing with wires will probably do stuff to them.\n" +
|
||||
"Wire layouts are different each round, " +
|
||||
"but consistent between machines of the same type.";
|
||||
|
||||
public HelpPopup()
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(Text);
|
||||
label.SetMessage(Loc.GetString("wires-menu-help-popup"));
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
StyleClasses = {ExamineSystem.StyleClassEntityTooltip},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
using Content.Client.Gameplay;
|
||||
using Content.Client.Mapping;
|
||||
using Robust.Client.State;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class MappingEditorTest
|
||||
{
|
||||
[Test]
|
||||
public async Task StopHardCodingWidgetsJesusChristTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true
|
||||
});
|
||||
var client = pair.Client;
|
||||
var state = client.ResolveDependency<IStateManager>();
|
||||
|
||||
await client.WaitPost(() =>
|
||||
{
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
state.RequestStateChange<MappingState>();
|
||||
});
|
||||
});
|
||||
|
||||
// arbitrary short time
|
||||
await client.WaitRunTicks(30);
|
||||
|
||||
await client.WaitPost(() =>
|
||||
{
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
state.RequestStateChange<GameplayState>();
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -98,4 +98,24 @@ public sealed class ResearchTest
|
|||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AllLatheRecipesValidTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
|
||||
var server = pair.Server;
|
||||
var proto = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var recipe in proto.EnumeratePrototypes<LatheRecipePrototype>())
|
||||
{
|
||||
if (recipe.Result == null)
|
||||
Assert.That(recipe.ResultReagents, Is.Not.Null, $"Recipe '{recipe.ID}' has no result or result reagents.");
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,15 @@ using Content.Server.Hands.Systems;
|
|||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
@ -82,9 +86,11 @@ namespace Content.Server.Administration.Commands
|
|||
return false;
|
||||
|
||||
HumanoidCharacterProfile? profile = null;
|
||||
ICommonSession? session = null;
|
||||
// Check if we are setting the outfit of a player to respect the preferences
|
||||
if (entityManager.TryGetComponent(target, out ActorComponent? actorComponent))
|
||||
{
|
||||
session = actorComponent.PlayerSession;
|
||||
var userId = actorComponent.PlayerSession.UserId;
|
||||
var preferencesManager = IoCManager.Resolve<IServerPreferencesManager>();
|
||||
var prefs = preferencesManager.GetPreferences(userId);
|
||||
|
|
@ -128,6 +134,36 @@ namespace Content.Server.Administration.Commands
|
|||
}
|
||||
}
|
||||
|
||||
// See if this starting gear is associated with a job
|
||||
var jobs = prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (job.StartingGear != gear)
|
||||
continue;
|
||||
|
||||
var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID);
|
||||
if (!prototypeManager.TryIndex<RoleLoadoutPrototype>(jobProtoId, out var jobProto))
|
||||
break;
|
||||
|
||||
// Don't require a player, so this works on Urists
|
||||
profile ??= entityManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var comp)
|
||||
? HumanoidCharacterProfile.DefaultWithSpecies(comp.Species)
|
||||
: new HumanoidCharacterProfile();
|
||||
// Try to get the user's existing loadout for the role
|
||||
profile.Loadouts.TryGetValue(jobProtoId, out var roleLoadout);
|
||||
|
||||
if (roleLoadout == null)
|
||||
{
|
||||
// If they don't have a loadout for the role, make a default one
|
||||
roleLoadout = new RoleLoadout(jobProtoId);
|
||||
roleLoadout.SetDefault(profile, session, prototypeManager);
|
||||
}
|
||||
|
||||
// Equip the target with the job loadout
|
||||
var stationSpawning = entityManager.System<SharedStationSpawningSystem>();
|
||||
stationSpawning.EquipRoleLoadout(target, roleLoadout, jobProto);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ public sealed class AdminSystem : EntitySystem
|
|||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
|
||||
SubscribeLocalEvent<ActorComponent, EntityRenamedEvent>(OnPlayerRenamed);
|
||||
}
|
||||
|
||||
private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
|
||||
|
|
@ -124,6 +125,11 @@ public sealed class AdminSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
private void OnPlayerRenamed(Entity<ActorComponent> ent, ref EntityRenamedEvent args)
|
||||
{
|
||||
UpdatePlayerList(ent.Comp.PlayerSession);
|
||||
}
|
||||
|
||||
public void UpdatePlayerList(ICommonSession player)
|
||||
{
|
||||
_playerList[player.UserId] = GetPlayerInfo(player.Data, player);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
|
|
@ -10,6 +11,7 @@ using Content.Shared.Popups;
|
|||
using Content.Shared.Power;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
|
|
@ -25,6 +27,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -40,6 +43,34 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var sync, out var xform))
|
||||
{
|
||||
if (sync.ConnectedAnomaly is null)
|
||||
continue;
|
||||
|
||||
if (_timing.CurTime < sync.NextCheckTime)
|
||||
continue;
|
||||
sync.NextCheckTime += sync.CheckFrequency;
|
||||
|
||||
if (Transform(sync.ConnectedAnomaly.Value).MapUid != Transform(uid).MapUid)
|
||||
{
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!xform.Coordinates.TryDistance(EntityManager, Transform(sync.ConnectedAnomaly.Value).Coordinates, out var distance))
|
||||
continue;
|
||||
|
||||
if (distance > sync.AttachRange)
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If powered, try to attach a nearby anomaly.
|
||||
/// </summary>
|
||||
|
|
@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnomalyComponent>(ent.Comp.ConnectedAnomaly, out var anomaly))
|
||||
if (ent.Comp.ConnectedAnomaly is null)
|
||||
return;
|
||||
|
||||
DisconnectFromAnomaly(ent, anomaly);
|
||||
DisconnectFromAnomaly(ent, ent.Comp.ConnectedAnomaly.Value);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
|
||||
|
|
@ -125,13 +156,16 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
|
||||
//TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid other)
|
||||
{
|
||||
if (ent.Comp.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
if (TryComp<AnomalyComponent>(other, out var anomaly))
|
||||
{
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large);
|
||||
_audio.PlayPvs(ent.Comp.ConnectedSound, ent);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Content.Server.Abilities.Psionics; //Nyano - Summary: the psniocs bin where dispel is located.
|
||||
using Content.Server.Abilities.Psionics;
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
|
@ -7,18 +7,18 @@ namespace Content.Server.Anomaly;
|
|||
|
||||
public sealed partial class AnomalySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAnomalySystem _sharedAnomaly = default!;
|
||||
[Dependency] private readonly DispelPowerSystem _dispel = default!;
|
||||
|
||||
private void InitializePsionics()
|
||||
{
|
||||
SubscribeLocalEvent<AnomalyComponent, DispelledEvent>(OnDispelled);
|
||||
}
|
||||
|
||||
//Nyano - Summary: gives dispellable behavior to Anomalies.
|
||||
private void OnDispelled(EntityUid uid, AnomalyComponent component, DispelledEvent args)
|
||||
private void OnDispelled(Entity<AnomalyComponent> ent, ref DispelledEvent args)
|
||||
{
|
||||
_dispel.DealDispelDamage(uid);
|
||||
_sharedAnomaly.ChangeAnomalyHealth(uid, 0 - _random.NextFloat(0.4f, 0.8f), component);
|
||||
_dispel.DealDispelDamage(ent);
|
||||
ChangeAnomalyHealth(ent, 0 - _random.NextFloat(0.4f, 0.8f), ent.Comp);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||
SubscribeLocalEvent<AnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||
|
||||
InitializePsionics(); //Nyano - Summary: stats up psionic related behavior.
|
||||
|
||||
InitializeGenerator();
|
||||
InitializeScanner();
|
||||
InitializeVessel();
|
||||
|
|
@ -87,7 +88,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||
|
||||
private void OnShutdown(Entity<AnomalyComponent> anomaly, ref ComponentShutdown args)
|
||||
{
|
||||
EndAnomaly(anomaly);
|
||||
if (anomaly.Comp.CurrentBehavior is not null)
|
||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||
|
||||
EndAnomaly(anomaly, spawnCore: false);
|
||||
}
|
||||
|
||||
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Content.Server.Anomaly.Components;
|
|||
/// <summary>
|
||||
/// a device that allows you to translate anomaly activity into multitool signals.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))]
|
||||
[RegisterComponent, AutoGenerateComponentPause, Access(typeof(AnomalySynchronizerSystem))]
|
||||
public sealed partial class AnomalySynchronizerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -34,6 +34,15 @@ public sealed partial class AnomalySynchronizerComponent : Component
|
|||
[DataField]
|
||||
public float AttachRange = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Periodicheski checks to see if the anomaly has moved to disconnect it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan CheckFrequency = TimeSpan.FromSeconds(1f);
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextCheckTime = TimeSpan.Zero;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> DecayingPort = "Decaying";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,236 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
|
||||
private readonly Color _messageColor = Color.FromSrgb(new Color(201, 22, 94));
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyInjectorComponent, StartCollideEvent>(OnStartCollideInjector);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalyPulseEvent>(OnAnomalyPulse);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalyShutdownEvent>(OnAnomalyShutdown);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalySupercriticalEvent>(OnAnomalySupercritical);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalySeverityChangedEvent>(OnSeverityChanged);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<AnomalyComponent, ActionAnomalyPulseEvent>(OnActionPulse);
|
||||
}
|
||||
|
||||
private void OnActionPulse(Entity<AnomalyComponent> ent, ref ActionAnomalyPulseEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(ent, ent.Comp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnStartCollideInjector(Entity<InnerBodyAnomalyInjectorComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.OtherEntity))
|
||||
return;
|
||||
if (TryComp<InnerBodyAnomalyComponent>(args.OtherEntity, out var innerAnom) && innerAnom.Injected)
|
||||
return;
|
||||
if (!_mind.TryGetMind(args.OtherEntity, out _, out var mindComponent))
|
||||
return;
|
||||
|
||||
EntityManager.AddComponents(args.OtherEntity, ent.Comp.InjectionComponents);
|
||||
QueueDel(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<InnerBodyAnomalyComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
AddAnomalyToBody(ent);
|
||||
}
|
||||
|
||||
private void AddAnomalyToBody(Entity<InnerBodyAnomalyComponent> ent)
|
||||
{
|
||||
if (!_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
return;
|
||||
|
||||
if (ent.Comp.Injected)
|
||||
return;
|
||||
|
||||
ent.Comp.Injected = true;
|
||||
|
||||
EntityManager.AddComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.StartSound is not null)
|
||||
_audio.PlayPvs(ent.Comp.StartSound, ent);
|
||||
|
||||
if (ent.Comp.StartMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.StartMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly,LogImpact.Extreme,$"{ToPrettyString(ent)} became anomaly host.");
|
||||
}
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnAnomalyPulse(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyPulseEvent args)
|
||||
{
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
}
|
||||
|
||||
private void OnAnomalySupercritical(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(ent, out var body))
|
||||
return;
|
||||
|
||||
_body.GibBody(ent, true, body, splatModifier: 5f);
|
||||
}
|
||||
|
||||
private void OnSeverityChanged(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
if (!_mind.TryGetMind(ent, out _, out var mindComponent) || mindComponent.Session == null)
|
||||
return;
|
||||
|
||||
var message = string.Empty;
|
||||
|
||||
if (args.Severity >= 0.5 && ent.Comp.LastSeverityInformed < 0.5)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.5f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-50");
|
||||
}
|
||||
if (args.Severity >= 0.75 && ent.Comp.LastSeverityInformed < 0.75)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.75f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-75");
|
||||
}
|
||||
if (args.Severity >= 0.9 && ent.Comp.LastSeverityInformed < 0.9)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.9f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-90");
|
||||
}
|
||||
if (args.Severity >= 1 && ent.Comp.LastSeverityInformed < 1)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 1f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-100");
|
||||
}
|
||||
|
||||
if (message == string.Empty)
|
||||
return;
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<InnerBodyAnomalyComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead)
|
||||
return;
|
||||
|
||||
_anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it
|
||||
}
|
||||
|
||||
private void OnAnomalyShutdown(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyShutdownEvent args)
|
||||
{
|
||||
RemoveAnomalyFromBody(ent);
|
||||
RemCompDeferred<InnerBodyAnomalyComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
RemoveAnomalyFromBody(ent);
|
||||
}
|
||||
|
||||
private void RemoveAnomalyFromBody(Entity<InnerBodyAnomalyComponent> ent)
|
||||
{
|
||||
if (!ent.Comp.Injected)
|
||||
return;
|
||||
|
||||
if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
EntityManager.RemoveComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.EndMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.EndMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly, LogImpact.Medium,$"{ToPrettyString(ent)} is no longer a host for the anomaly.");
|
||||
}
|
||||
|
||||
ent.Comp.Injected = false;
|
||||
RemCompDeferred<AnomalyComponent>(ent);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public sealed class TechAnomalySystem : EntitySystem
|
|||
if (_timing.CurTime < tech.NextTimer)
|
||||
continue;
|
||||
|
||||
tech.NextTimer += TimeSpan.FromSeconds(tech.TimerFrequency * anom.Stability);
|
||||
tech.NextTimer += TimeSpan.FromSeconds(tech.TimerFrequency);
|
||||
|
||||
_signal.InvokePort(uid, tech.TimerPort);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Server.Decals;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
[Dependency] private readonly DecalSystem _decalSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private const int HotspotSoundCooldownCycles = 200;
|
||||
|
||||
private int _hotspotSoundCooldown = 0;
|
||||
|
|
@ -56,7 +58,30 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
if (tile.Hotspot.Bypassing)
|
||||
{
|
||||
tile.Hotspot.State = 3;
|
||||
// TODO ATMOS: Burn tile here
|
||||
|
||||
var gridUid = ent.Owner;
|
||||
var tilePos = tile.GridIndices;
|
||||
|
||||
// Get the existing decals on the tile
|
||||
var tileDecals = _decalSystem.GetDecalsInRange(gridUid, tilePos);
|
||||
|
||||
// Count the burnt decals on the tile
|
||||
var tileBurntDecals = 0;
|
||||
|
||||
foreach (var set in tileDecals)
|
||||
{
|
||||
if (Array.IndexOf(_burntDecals, set.Decal.Id) == -1)
|
||||
continue;
|
||||
|
||||
tileBurntDecals++;
|
||||
|
||||
if (tileBurntDecals > 4)
|
||||
break;
|
||||
}
|
||||
|
||||
// Add a random burned decal to the tile only if there are less than 4 of them
|
||||
if (tileBurntDecals < 4)
|
||||
_decalSystem.TryAddDecal(_burntDecals[_random.Next(_burntDecals.Length)], new EntityCoordinates(gridUid, tilePos), out _, cleanable: true);
|
||||
|
||||
if (tile.Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Server.Body.Systems;
|
|||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
|
|
@ -12,7 +13,9 @@ using Robust.Shared.Audio.Systems;
|
|||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
|
|
@ -36,6 +39,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] public readonly PuddleSystem Puddle = default!;
|
||||
|
||||
private const float ExposedUpdateDelay = 1f;
|
||||
|
|
@ -47,6 +51,8 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
private EntityQuery<FirelockComponent> _firelockQuery;
|
||||
private HashSet<EntityUid> _entSet = new();
|
||||
|
||||
private string[] _burntDecals = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -66,7 +72,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
_firelockQuery = GetEntityQuery<FirelockComponent>();
|
||||
|
||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
|
||||
CacheDecals();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
|
@ -81,6 +89,12 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
InvalidateTile(ev.NewTile.GridUid, ev.NewTile.GridIndices);
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev)
|
||||
{
|
||||
if (ev.WasModified<DecalPrototype>())
|
||||
CacheDecals();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
|
@ -107,4 +121,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
|
||||
_exposedTimer -= ExposedUpdateDelay;
|
||||
}
|
||||
|
||||
private void CacheDecals()
|
||||
{
|
||||
_burntDecals = _prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ using Content.Server.Administration.Logs;
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
|
|
@ -13,6 +13,7 @@ using Content.Shared.Database;
|
|||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
|
@ -39,6 +40,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
|||
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceDisabledEvent>(OnPumpLeaveAtmosphere);
|
||||
SubscribeLocalEvent<GasPressurePumpComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<GasPressurePumpComponent, ActivateInWorldEvent>(OnPumpActivate);
|
||||
SubscribeLocalEvent<GasPressurePumpComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpChangeOutputPressureMessage>(OnOutputPressureChangeMessage);
|
||||
SubscribeLocalEvent<GasPressurePumpComponent, GasPressurePumpToggleStatusMessage>(OnToggleStatusMessage);
|
||||
|
|
@ -63,9 +65,15 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
|||
}
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, GasPressurePumpComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!pump.Enabled
|
||||
|| (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|
||||
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
|
|
@ -154,7 +162,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
|||
if (!Resolve(uid, ref pump, ref appearance, false))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, PumpVisuals.Enabled, pump.Enabled, appearance);
|
||||
bool pumpOn = pump.Enabled && (TryComp<ApcPowerReceiverComponent>(uid, out var power) && power.Powered);
|
||||
_appearance.SetData(uid, PumpVisuals.Enabled, pumpOn, appearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ using Content.Server.Atmos.Piping.Components;
|
|||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Audio;
|
||||
|
|
@ -17,6 +17,7 @@ using Content.Shared.DeviceNetwork;
|
|||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
|
@ -45,6 +46,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
|||
SubscribeLocalEvent<GasVolumePumpComponent, AtmosDeviceDisabledEvent>(OnVolumePumpLeaveAtmosphere);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, ActivateInWorldEvent>(OnPumpActivate);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, GasVolumePumpChangeTransferRateMessage>(OnTransferRateChangeMessage);
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, GasVolumePumpToggleStatusMessage>(OnToggleStatusMessage);
|
||||
|
|
@ -69,9 +71,15 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
|||
args.PushMarkup(str);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, GasVolumePumpComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
UpdateAppearance(uid, component);
|
||||
}
|
||||
|
||||
private void OnVolumePumpUpdated(EntityUid uid, GasVolumePumpComponent pump, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!pump.Enabled ||
|
||||
(TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered) ||
|
||||
!_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
|
|
@ -183,7 +191,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
|||
if (!Resolve(uid, ref pump, ref appearance, false))
|
||||
return;
|
||||
|
||||
if (!pump.Enabled)
|
||||
bool pumpOn = pump.Enabled && (TryComp<ApcPowerReceiverComponent>(uid, out var power) && power.Powered);
|
||||
if (!pumpOn)
|
||||
_appearance.SetData(uid, GasVolumePumpVisuals.State, GasVolumePumpState.Off, appearance);
|
||||
else if (pump.Blocked)
|
||||
_appearance.SetData(uid, GasVolumePumpVisuals.State, GasVolumePumpState.Blocked, appearance);
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
|||
Loc.GetString(
|
||||
"earlyleave-cryo-announcement",
|
||||
("character", name),
|
||||
("entity", ent.Owner),
|
||||
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
|
||||
), Loc.GetString("earlyleave-cryo-sender"),
|
||||
playDefaultSound: false
|
||||
|
|
|
|||
|
|
@ -6,90 +6,90 @@ namespace Content.Server.Botany.Components;
|
|||
[RegisterComponent]
|
||||
public sealed partial class PlantHolderComponent : Component
|
||||
{
|
||||
[DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("updateDelay")]
|
||||
[DataField]
|
||||
public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3);
|
||||
|
||||
[DataField("lastProduce")]
|
||||
[DataField]
|
||||
public int LastProduce;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("missingGas")]
|
||||
[DataField]
|
||||
public int MissingGas;
|
||||
|
||||
[DataField("cycleDelay")]
|
||||
[DataField]
|
||||
public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f);
|
||||
|
||||
[DataField("lastCycle", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan LastCycle = TimeSpan.Zero;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("updateSpriteAfterUpdate")]
|
||||
[DataField]
|
||||
public bool UpdateSpriteAfterUpdate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("drawWarnings")]
|
||||
[DataField]
|
||||
public bool DrawWarnings = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("waterLevel")]
|
||||
[DataField]
|
||||
public float WaterLevel = 100f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("nutritionLevel")]
|
||||
[DataField]
|
||||
public float NutritionLevel = 100f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("pestLevel")]
|
||||
[DataField]
|
||||
public float PestLevel;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("weedLevel")]
|
||||
[DataField]
|
||||
public float WeedLevel;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("toxins")]
|
||||
[DataField]
|
||||
public float Toxins;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("age")]
|
||||
[DataField]
|
||||
public int Age;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("skipAging")]
|
||||
[DataField]
|
||||
public int SkipAging;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("dead")]
|
||||
[DataField]
|
||||
public bool Dead;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("harvest")]
|
||||
[DataField]
|
||||
public bool Harvest;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("sampled")]
|
||||
[DataField]
|
||||
public bool Sampled;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("yieldMod")]
|
||||
[DataField]
|
||||
public int YieldMod = 1;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("mutationMod")]
|
||||
[DataField]
|
||||
public float MutationMod = 1f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("mutationLevel")]
|
||||
[DataField]
|
||||
public float MutationLevel;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("health")]
|
||||
[DataField]
|
||||
public float Health;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("weedCoefficient")]
|
||||
[DataField]
|
||||
public float WeedCoefficient = 1f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("seed")]
|
||||
[DataField]
|
||||
public SeedData? Seed;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("improperHeat")]
|
||||
[DataField]
|
||||
public bool ImproperHeat;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("improperPressure")]
|
||||
[DataField]
|
||||
public bool ImproperPressure;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("improperLight")]
|
||||
[DataField]
|
||||
public bool ImproperLight;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("forceUpdate")]
|
||||
[DataField]
|
||||
public bool ForceUpdate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("solution")]
|
||||
[DataField]
|
||||
public string SoilSolutionName = "soil";
|
||||
|
||||
[DataField]
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ public sealed partial class ProduceComponent : SharedProduceComponent
|
|||
/// <summary>
|
||||
/// Seed data used to create a <see cref="SeedComponent"/> when this produce has its seeds extracted.
|
||||
/// </summary>
|
||||
[DataField("seed")]
|
||||
[DataField]
|
||||
public SeedData? Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Seed data used to create a <see cref="SeedComponent"/> when this produce has its seeds extracted.
|
||||
/// </summary>
|
||||
[DataField("seedId", customTypeSerializer: typeof(PrototypeIdSerializer<SeedPrototype>))]
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<SeedPrototype>))]
|
||||
public string? SeedId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Content.Server.Botany.Components;
|
|||
using Content.Server.Botany.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
|
@ -132,78 +133,67 @@ public partial class SeedData
|
|||
[DataField("productPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> ProductPrototypes = new();
|
||||
|
||||
[DataField("chemicals")] public Dictionary<string, SeedChemQuantity> Chemicals = new();
|
||||
[DataField] public Dictionary<string, SeedChemQuantity> Chemicals = new();
|
||||
|
||||
[DataField("consumeGasses")] public Dictionary<Gas, float> ConsumeGasses = new();
|
||||
[DataField] public Dictionary<Gas, float> ConsumeGasses = new();
|
||||
|
||||
[DataField("exudeGasses")] public Dictionary<Gas, float> ExudeGasses = new();
|
||||
[DataField] public Dictionary<Gas, float> ExudeGasses = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tolerances
|
||||
|
||||
[DataField("nutrientConsumption")] public float NutrientConsumption = 0.75f;
|
||||
[DataField] public float NutrientConsumption = 0.75f;
|
||||
|
||||
[DataField("waterConsumption")] public float WaterConsumption = 0.5f;
|
||||
[DataField("idealHeat")] public float IdealHeat = 293f;
|
||||
[DataField("heatTolerance")] public float HeatTolerance = 10f;
|
||||
[DataField("idealLight")] public float IdealLight = 7f;
|
||||
[DataField("lightTolerance")] public float LightTolerance = 3f;
|
||||
[DataField("toxinsTolerance")] public float ToxinsTolerance = 4f;
|
||||
[DataField] public float WaterConsumption = 0.5f;
|
||||
[DataField] public float IdealHeat = 293f;
|
||||
[DataField] public float HeatTolerance = 10f;
|
||||
[DataField] public float IdealLight = 7f;
|
||||
[DataField] public float LightTolerance = 3f;
|
||||
[DataField] public float ToxinsTolerance = 4f;
|
||||
|
||||
[DataField("lowPressureTolerance")] public float LowPressureTolerance = 81f;
|
||||
[DataField] public float LowPressureTolerance = 81f;
|
||||
|
||||
[DataField("highPressureTolerance")] public float HighPressureTolerance = 121f;
|
||||
[DataField] public float HighPressureTolerance = 121f;
|
||||
|
||||
[DataField("pestTolerance")] public float PestTolerance = 5f;
|
||||
[DataField] public float PestTolerance = 5f;
|
||||
|
||||
[DataField("weedTolerance")] public float WeedTolerance = 5f;
|
||||
[DataField] public float WeedTolerance = 5f;
|
||||
|
||||
[DataField("weedHighLevelThreshold")] public float WeedHighLevelThreshold = 10f;
|
||||
[DataField] public float WeedHighLevelThreshold = 10f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region General traits
|
||||
|
||||
[DataField("endurance")] public float Endurance = 100f;
|
||||
[DataField] public float Endurance = 100f;
|
||||
|
||||
[DataField("yield")] public int Yield;
|
||||
[DataField("lifespan")] public float Lifespan;
|
||||
[DataField("maturation")] public float Maturation;
|
||||
[DataField("production")] public float Production;
|
||||
[DataField("growthStages")] public int GrowthStages = 6;
|
||||
[DataField] public int Yield;
|
||||
[DataField] public float Lifespan;
|
||||
[DataField] public float Maturation;
|
||||
[DataField] public float Production;
|
||||
[DataField] public int GrowthStages = 6;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("harvestRepeat")] public HarvestType HarvestRepeat = HarvestType.NoRepeat;
|
||||
[DataField] public HarvestType HarvestRepeat = HarvestType.NoRepeat;
|
||||
|
||||
[DataField("potency")] public float Potency = 1f;
|
||||
[DataField] public float Potency = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, cannot be harvested for seeds. Balances hybrids and
|
||||
/// mutations.
|
||||
/// </summary>
|
||||
[DataField("seedless")] public bool Seedless = false;
|
||||
[DataField] public bool Seedless = false;
|
||||
|
||||
/// <summary>
|
||||
/// If false, rapidly decrease health while growing. Used to kill off
|
||||
/// plants with "bad" mutations.
|
||||
/// </summary>
|
||||
[DataField("viable")] public bool Viable = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, fruit slips players.
|
||||
/// </summary>
|
||||
[DataField("slip")] public bool Slip = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, fruits are sentient.
|
||||
/// </summary>
|
||||
[DataField("sentient")] public bool Sentient = false;
|
||||
[DataField] public bool Viable = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, a sharp tool is required to harvest this plant.
|
||||
/// </summary>
|
||||
[DataField("ligneous")] public bool Ligneous;
|
||||
[DataField] public bool Ligneous;
|
||||
|
||||
// No, I'm not removing these.
|
||||
// if you re-add these, make sure that they get cloned.
|
||||
|
|
@ -222,36 +212,35 @@ public partial class SeedData
|
|||
|
||||
#region Cosmetics
|
||||
|
||||
[DataField("plantRsi", required: true)]
|
||||
[DataField(required: true)]
|
||||
public ResPath PlantRsi { get; set; } = default!;
|
||||
|
||||
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
|
||||
[DataField] public string PlantIconState { get; set; } = "produce";
|
||||
|
||||
/// <summary>
|
||||
/// Screams random sound, could be strict sound SoundPathSpecifier or collection SoundCollectionSpecifier
|
||||
/// base class is SoundSpecifier
|
||||
/// Screams random sound from collection SoundCollectionSpecifier
|
||||
/// </summary>
|
||||
[DataField("screamSound")]
|
||||
[DataField]
|
||||
public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("PlantScreams", AudioParams.Default.WithVolume(-10));
|
||||
|
||||
[DataField("screaming")] public bool CanScream;
|
||||
|
||||
[DataField("bioluminescent")] public bool Bioluminescent;
|
||||
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))] public string KudzuPrototype = "WeakKudzu";
|
||||
|
||||
public float BioluminescentRadius = 2f;
|
||||
|
||||
[DataField("kudzuPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))] public string KudzuPrototype = "WeakKudzu";
|
||||
|
||||
[DataField("turnIntoKudzu")] public bool TurnIntoKudzu;
|
||||
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
|
||||
[DataField] public bool TurnIntoKudzu;
|
||||
[DataField] public string? SplatPrototype { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The mutation effects that have been applied to this plant.
|
||||
/// </summary>
|
||||
[DataField] public List<RandomPlantMutation> Mutations { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The seed prototypes this seed may mutate into when prompted to.
|
||||
/// </summary>
|
||||
[DataField("mutationPrototypes", customTypeSerializer: typeof(PrototypeIdListSerializer<SeedPrototype>))]
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<SeedPrototype>))]
|
||||
public List<string> MutationPrototypes = new();
|
||||
|
||||
public SeedData Clone()
|
||||
|
|
@ -295,22 +284,20 @@ public partial class SeedData
|
|||
|
||||
Seedless = Seedless,
|
||||
Viable = Viable,
|
||||
Slip = Slip,
|
||||
Sentient = Sentient,
|
||||
Ligneous = Ligneous,
|
||||
|
||||
PlantRsi = PlantRsi,
|
||||
PlantIconState = PlantIconState,
|
||||
Bioluminescent = Bioluminescent,
|
||||
CanScream = CanScream,
|
||||
TurnIntoKudzu = TurnIntoKudzu,
|
||||
BioluminescentColor = BioluminescentColor,
|
||||
SplatPrototype = SplatPrototype,
|
||||
Mutations = new List<RandomPlantMutation>(),
|
||||
|
||||
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
|
||||
Unique = true,
|
||||
};
|
||||
|
||||
newSeed.Mutations.AddRange(Mutations);
|
||||
return newSeed;
|
||||
}
|
||||
|
||||
|
|
@ -356,18 +343,16 @@ public partial class SeedData
|
|||
HarvestRepeat = HarvestRepeat,
|
||||
Potency = Potency,
|
||||
|
||||
Mutations = Mutations,
|
||||
|
||||
Seedless = Seedless,
|
||||
Viable = Viable,
|
||||
Slip = Slip,
|
||||
Sentient = Sentient,
|
||||
Ligneous = Ligneous,
|
||||
|
||||
PlantRsi = other.PlantRsi,
|
||||
PlantIconState = other.PlantIconState,
|
||||
Bioluminescent = Bioluminescent,
|
||||
CanScream = CanScream,
|
||||
TurnIntoKudzu = TurnIntoKudzu,
|
||||
BioluminescentColor = BioluminescentColor,
|
||||
SplatPrototype = other.SplatPrototype,
|
||||
|
||||
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
|
@ -10,6 +11,15 @@ public sealed partial class BotanySystem
|
|||
if (!TryGetSeed(produce, out var seed))
|
||||
return;
|
||||
|
||||
foreach (var mutation in seed.Mutations)
|
||||
{
|
||||
if (mutation.AppliesToProduce)
|
||||
{
|
||||
var args = new EntityEffectBaseArgs(uid, EntityManager);
|
||||
mutation.Effect.Effect(args);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_solutionContainerSystem.EnsureSolution(uid,
|
||||
produce.SolutionName,
|
||||
out var solutionContainer,
|
||||
|
|
|
|||
|
|
@ -5,16 +5,11 @@ using Content.Shared.Chemistry.EntitySystems;
|
|||
using Content.Shared.Botany;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.StepTrigger.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
|
@ -34,7 +29,6 @@ public sealed partial class BotanySystem : EntitySystem
|
|||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
|
||||
[Dependency] private readonly CollisionWakeSystem _colWakeSystem = default!;
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
|
@ -183,30 +177,6 @@ public sealed partial class BotanySystem : EntitySystem
|
|||
_metaData.SetEntityDescription(entity,
|
||||
metaData.EntityDescription + " " + Loc.GetString("botany-mysterious-description-addon"), metaData);
|
||||
}
|
||||
|
||||
if (proto.Bioluminescent)
|
||||
{
|
||||
var light = _light.EnsureLight(entity);
|
||||
_light.SetRadius(entity, proto.BioluminescentRadius, light);
|
||||
_light.SetColor(entity, proto.BioluminescentColor, light);
|
||||
// TODO: Ayo why you copy-pasting code between here and plantholder?
|
||||
_light.SetCastShadows(entity, false, light); // this is expensive, and botanists make lots of plants
|
||||
}
|
||||
|
||||
if (proto.Slip)
|
||||
{
|
||||
var slippery = EnsureComp<SlipperyComponent>(entity);
|
||||
Dirty(entity, slippery);
|
||||
EnsureComp<StepTriggerComponent>(entity);
|
||||
// Need a fixture with a slip layer in order to actually do the slipping
|
||||
var fixtures = EnsureComp<FixturesComponent>(entity);
|
||||
var body = EnsureComp<PhysicsComponent>(entity);
|
||||
var shape = fixtures.Fixtures["fix1"].Shape;
|
||||
_fixtureSystem.TryCreateFixture(entity, shape, "slips", 1, false, (int) CollisionGroup.SlipLayer, manager: fixtures, body: body);
|
||||
// Need to disable collision wake so that mobs can collide with and slip on it
|
||||
var collisionWake = EnsureComp<CollisionWakeComponent>(entity);
|
||||
_colWakeSystem.SetEnabled(entity, false, collisionWake);
|
||||
}
|
||||
}
|
||||
|
||||
return products;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using Content.Shared.Atmos;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using System.Linq;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Botany;
|
||||
|
||||
|
|
@ -11,25 +11,40 @@ public sealed class MutationSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private WeightedRandomFillSolutionPrototype _randomChems = default!;
|
||||
|
||||
private RandomPlantMutationListPrototype _randomMutations = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_randomChems = _prototypeManager.Index<WeightedRandomFillSolutionPrototype>("RandomPickBotanyReagent");
|
||||
_randomMutations = _prototypeManager.Index<RandomPlantMutationListPrototype>("RandomPlantMutations");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main idea: Simulate genetic mutation using random binary flips. Each
|
||||
/// seed attribute can be encoded with a variable number of bits, e.g.
|
||||
/// NutrientConsumption is represented by 5 bits randomly distributed in the
|
||||
/// plant's genome which thermometer code the floating value between 0.1 and
|
||||
/// 5. 1 unit of mutation flips one bit in the plant's genome, which changes
|
||||
/// NutrientConsumption if one of those 5 bits gets affected.
|
||||
///
|
||||
/// You MUST clone() seed before mutating it!
|
||||
/// For each random mutation, see if it occurs on this plant this check.
|
||||
/// </summary>
|
||||
public void MutateSeed(ref SeedData seed, float severity)
|
||||
/// <param name="seed"></param>
|
||||
/// <param name="severity"></param>
|
||||
public void CheckRandomMutations(EntityUid plantHolder, ref SeedData seed, float severity)
|
||||
{
|
||||
foreach (var mutation in _randomMutations.mutations)
|
||||
{
|
||||
if (Random(mutation.BaseOdds * severity))
|
||||
{
|
||||
if (mutation.AppliesToPlant)
|
||||
{
|
||||
var args = new EntityEffectBaseArgs(plantHolder, EntityManager);
|
||||
mutation.Effect.Effect(args);
|
||||
}
|
||||
// Stat adjustments do not persist by being an attached effect, they just change the stat.
|
||||
if (mutation.Persists && !seed.Mutations.Any(m => m.Name == mutation.Name))
|
||||
seed.Mutations.Add(mutation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks all defined mutations against a seed to see which of them are applied.
|
||||
/// </summary>
|
||||
public void MutateSeed(EntityUid plantHolder, ref SeedData seed, float severity)
|
||||
{
|
||||
if (!seed.Unique)
|
||||
{
|
||||
|
|
@ -37,57 +52,7 @@ public sealed class MutationSystem : EntitySystem
|
|||
return;
|
||||
}
|
||||
|
||||
// Add up everything in the bits column and put the number here.
|
||||
const int totalbits = 262;
|
||||
|
||||
#pragma warning disable IDE0055 // disable formatting warnings because this looks more readable
|
||||
// Tolerances (55)
|
||||
MutateFloat(ref seed.NutrientConsumption , 0.05f, 1.2f, 5, totalbits, severity);
|
||||
MutateFloat(ref seed.WaterConsumption , 3f , 9f , 5, totalbits, severity);
|
||||
MutateFloat(ref seed.IdealHeat , 263f , 323f, 5, totalbits, severity);
|
||||
MutateFloat(ref seed.HeatTolerance , 2f , 25f , 5, totalbits, severity);
|
||||
MutateFloat(ref seed.IdealLight , 0f , 14f , 5, totalbits, severity);
|
||||
MutateFloat(ref seed.LightTolerance , 1f , 5f , 5, totalbits, severity);
|
||||
MutateFloat(ref seed.ToxinsTolerance , 1f , 10f , 5, totalbits, severity);
|
||||
MutateFloat(ref seed.LowPressureTolerance , 60f , 100f, 5, totalbits, severity);
|
||||
MutateFloat(ref seed.HighPressureTolerance, 100f , 140f, 5, totalbits, severity);
|
||||
MutateFloat(ref seed.PestTolerance , 0f , 15f , 5, totalbits, severity);
|
||||
MutateFloat(ref seed.WeedTolerance , 0f , 15f , 5, totalbits, severity);
|
||||
|
||||
// Stats (30*2 = 60)
|
||||
MutateFloat(ref seed.Endurance , 50f , 150f, 5, totalbits, 2 * severity);
|
||||
MutateInt(ref seed.Yield , 3 , 10 , 5, totalbits, 2 * severity);
|
||||
MutateFloat(ref seed.Lifespan , 10f , 80f , 5, totalbits, 2 * severity);
|
||||
MutateFloat(ref seed.Maturation , 3f , 8f , 5, totalbits, 2 * severity);
|
||||
MutateFloat(ref seed.Production , 1f , 10f , 5, totalbits, 2 * severity);
|
||||
MutateFloat(ref seed.Potency , 30f , 100f, 5, totalbits, 2 * severity);
|
||||
|
||||
// Kill the plant (30)
|
||||
MutateBool(ref seed.Viable , false, 30, totalbits, severity);
|
||||
|
||||
// Fun (72)
|
||||
MutateBool(ref seed.Seedless , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.Slip , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.Sentient , true , 2 , totalbits, severity);
|
||||
MutateBool(ref seed.Ligneous , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.Bioluminescent, true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.TurnIntoKudzu , true , 10, totalbits, severity);
|
||||
MutateBool(ref seed.CanScream , true , 10, totalbits, severity);
|
||||
seed.BioluminescentColor = RandomColor(seed.BioluminescentColor, 10, totalbits, severity);
|
||||
#pragma warning restore IDE0055
|
||||
|
||||
// ConstantUpgade (10)
|
||||
MutateHarvestType(ref seed.HarvestRepeat, 10, totalbits, severity);
|
||||
|
||||
// Gas (5)
|
||||
MutateGasses(ref seed.ExudeGasses, 0.01f, 0.5f, 4, totalbits, severity);
|
||||
MutateGasses(ref seed.ConsumeGasses, 0.01f, 0.5f, 1, totalbits, severity);
|
||||
|
||||
// Chems (20)
|
||||
MutateChemicals(ref seed.Chemicals, 20, totalbits, severity);
|
||||
|
||||
// Species (10)
|
||||
MutateSpecies(ref seed, 10, totalbits, severity);
|
||||
CheckRandomMutations(plantHolder, ref seed, severity);
|
||||
}
|
||||
|
||||
public SeedData Cross(SeedData a, SeedData b)
|
||||
|
|
@ -115,19 +80,18 @@ public sealed class MutationSystem : EntitySystem
|
|||
CrossFloat(ref result.Production, a.Production);
|
||||
CrossFloat(ref result.Potency, a.Potency);
|
||||
|
||||
// we do not transfer Sentient to another plant to avoid ghost role spam
|
||||
CrossBool(ref result.Seedless, a.Seedless);
|
||||
CrossBool(ref result.Viable, a.Viable);
|
||||
CrossBool(ref result.Slip, a.Slip);
|
||||
CrossBool(ref result.Ligneous, a.Ligneous);
|
||||
CrossBool(ref result.Bioluminescent, a.Bioluminescent);
|
||||
CrossBool(ref result.TurnIntoKudzu, a.TurnIntoKudzu);
|
||||
CrossBool(ref result.CanScream, a.CanScream);
|
||||
|
||||
CrossGasses(ref result.ExudeGasses, a.ExudeGasses);
|
||||
CrossGasses(ref result.ConsumeGasses, a.ConsumeGasses);
|
||||
|
||||
result.BioluminescentColor = Random(0.5f) ? a.BioluminescentColor : result.BioluminescentColor;
|
||||
// LINQ Explanation
|
||||
// For the list of mutation effects on both plants, use a 50% chance to pick each one.
|
||||
// Union all of the chosen mutations into one list, and pick ones with a Distinct (unique) name.
|
||||
result.Mutations = result.Mutations.Where(m => Random(0.5f)).Union(a.Mutations.Where(m => Random(0.5f))).DistinctBy(m => m.Name).ToList();
|
||||
|
||||
// Hybrids have a high chance of being seedless. Balances very
|
||||
// effective hybrid crossings.
|
||||
|
|
@ -139,206 +103,6 @@ public sealed class MutationSystem : EntitySystem
|
|||
return result;
|
||||
}
|
||||
|
||||
// Mutate reference 'val' between 'min' and 'max' by pretending the value
|
||||
// is representable by a thermometer code with 'bits' number of bits and
|
||||
// randomly flipping some of them.
|
||||
//
|
||||
// 'totalbits' and 'mult' are used only to calculate the probability that
|
||||
// one bit gets flipped.
|
||||
private void MutateFloat(ref float val, float min, float max, int bits, int totalbits, float mult)
|
||||
{
|
||||
// Probability that a bit flip happens for this value's representation in thermometer code.
|
||||
float probBitflip = mult * bits / totalbits;
|
||||
probBitflip = Math.Clamp(probBitflip, 0, 1);
|
||||
if (!Random(probBitflip))
|
||||
return;
|
||||
|
||||
if (min == max)
|
||||
{
|
||||
val = min;
|
||||
return;
|
||||
}
|
||||
|
||||
// Starting number of bits that are high, between 0 and bits.
|
||||
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
||||
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
||||
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
||||
valInt = Math.Clamp(valInt, 0, bits);
|
||||
|
||||
// Probability that the bit flip increases n.
|
||||
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
|
||||
// In other words, it tends to go to the middle.
|
||||
float probIncrease = 1 - (float)valInt / bits;
|
||||
int valIntMutated;
|
||||
if (Random(probIncrease))
|
||||
{
|
||||
valIntMutated = valInt + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valIntMutated = valInt - 1;
|
||||
}
|
||||
|
||||
// Set value based on mutated thermometer code.
|
||||
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
|
||||
val = valMutated;
|
||||
}
|
||||
|
||||
private void MutateInt(ref int val, int min, int max, int bits, int totalbits, float mult)
|
||||
{
|
||||
// Probability that a bit flip happens for this value's representation in thermometer code.
|
||||
float probBitflip = mult * bits / totalbits;
|
||||
probBitflip = Math.Clamp(probBitflip, 0, 1);
|
||||
if (!Random(probBitflip))
|
||||
return;
|
||||
|
||||
if (min == max)
|
||||
{
|
||||
val = min;
|
||||
return;
|
||||
}
|
||||
|
||||
// Starting number of bits that are high, between 0 and bits.
|
||||
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
||||
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
||||
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
||||
valInt = Math.Clamp(valInt, 0, bits);
|
||||
|
||||
// Probability that the bit flip increases n.
|
||||
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
|
||||
// In other words, it tends to go to the middle.
|
||||
float probIncrease = 1 - (float)valInt / bits;
|
||||
int valMutated;
|
||||
if (Random(probIncrease))
|
||||
{
|
||||
valMutated = val + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valMutated = val - 1;
|
||||
}
|
||||
|
||||
valMutated = Math.Clamp(valMutated, min, max);
|
||||
val = valMutated;
|
||||
}
|
||||
|
||||
private void MutateBool(ref bool val, bool polarity, int bits, int totalbits, float mult)
|
||||
{
|
||||
// Probability that a bit flip happens for this value.
|
||||
float probSet = mult * bits / totalbits;
|
||||
probSet = Math.Clamp(probSet, 0, 1);
|
||||
if (!Random(probSet))
|
||||
return;
|
||||
|
||||
val = polarity;
|
||||
}
|
||||
|
||||
private void MutateHarvestType(ref HarvestType val, int bits, int totalbits, float mult)
|
||||
{
|
||||
float probModify = mult * bits / totalbits;
|
||||
probModify = Math.Clamp(probModify, 0, 1);
|
||||
|
||||
if (!Random(probModify))
|
||||
return;
|
||||
|
||||
if (val == HarvestType.NoRepeat)
|
||||
val = HarvestType.Repeat;
|
||||
else if (val == HarvestType.Repeat)
|
||||
val = HarvestType.SelfHarvest;
|
||||
}
|
||||
|
||||
private void MutateGasses(ref Dictionary<Gas, float> gasses, float min, float max, int bits, int totalbits, float mult)
|
||||
{
|
||||
float probModify = mult * bits / totalbits;
|
||||
probModify = Math.Clamp(probModify, 0, 1);
|
||||
if (!Random(probModify))
|
||||
return;
|
||||
|
||||
// Add a random amount of a random gas to this gas dictionary
|
||||
float amount = _robustRandom.NextFloat(min, max);
|
||||
Gas gas = _robustRandom.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
||||
if (gasses.ContainsKey(gas))
|
||||
{
|
||||
gasses[gas] += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
gasses.Add(gas, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void MutateChemicals(ref Dictionary<string, SeedChemQuantity> chemicals, int bits, int totalbits, float mult)
|
||||
{
|
||||
float probModify = mult * bits / totalbits;
|
||||
probModify = Math.Clamp(probModify, 0, 1);
|
||||
if (!Random(probModify))
|
||||
return;
|
||||
|
||||
// Add a random amount of a random chemical to this set of chemicals
|
||||
if (_randomChems != null)
|
||||
{
|
||||
var pick = _randomChems.Pick(_robustRandom);
|
||||
string chemicalId = pick.reagent;
|
||||
int amount = _robustRandom.Next(1, (int)pick.quantity);
|
||||
SeedChemQuantity seedChemQuantity = new SeedChemQuantity();
|
||||
if (chemicals.ContainsKey(chemicalId))
|
||||
{
|
||||
seedChemQuantity.Min = chemicals[chemicalId].Min;
|
||||
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
seedChemQuantity.Min = 1;
|
||||
seedChemQuantity.Max = 1 + amount;
|
||||
seedChemQuantity.Inherent = false;
|
||||
}
|
||||
int potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
|
||||
seedChemQuantity.PotencyDivisor = potencyDivisor;
|
||||
chemicals[chemicalId] = seedChemQuantity;
|
||||
}
|
||||
}
|
||||
|
||||
private void MutateSpecies(ref SeedData seed, int bits, int totalbits, float mult)
|
||||
{
|
||||
float p = mult * bits / totalbits;
|
||||
p = Math.Clamp(p, 0, 1);
|
||||
if (!Random(p))
|
||||
return;
|
||||
|
||||
if (seed.MutationPrototypes.Count == 0)
|
||||
return;
|
||||
|
||||
var targetProto = _robustRandom.Pick(seed.MutationPrototypes);
|
||||
_prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed);
|
||||
|
||||
if (protoSeed == null)
|
||||
{
|
||||
Log.Error($"Seed prototype could not be found: {targetProto}!");
|
||||
return;
|
||||
}
|
||||
|
||||
seed = seed.SpeciesChange(protoSeed);
|
||||
}
|
||||
|
||||
private Color RandomColor(Color color, int bits, int totalbits, float mult)
|
||||
{
|
||||
float probModify = mult * bits / totalbits;
|
||||
if (Random(probModify))
|
||||
{
|
||||
var colors = new List<Color>{
|
||||
Color.White,
|
||||
Color.Red,
|
||||
Color.Yellow,
|
||||
Color.Green,
|
||||
Color.Blue,
|
||||
Color.Purple,
|
||||
Color.Pink
|
||||
};
|
||||
return _robustRandom.Pick(colors);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
private void CrossChemicals(ref Dictionary<string, SeedChemQuantity> val, Dictionary<string, SeedChemQuantity> other)
|
||||
{
|
||||
// Go through chemicals from the pollen in swab
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Server.Fluids.Components;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
|
@ -79,7 +77,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
if (component.Seed == null)
|
||||
return 0;
|
||||
|
||||
var result = Math.Max(1, (int) (component.Age * component.Seed.GrowthStages / component.Seed.Maturation));
|
||||
var result = Math.Max(1, (int)(component.Age * component.Seed.GrowthStages / component.Seed.Maturation));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -125,9 +123,9 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
args.PushMarkup(Loc.GetString("plant-holder-component-pest-high-level-message"));
|
||||
|
||||
args.PushMarkup(Loc.GetString($"plant-holder-component-water-level-message",
|
||||
("waterLevel", (int) component.WaterLevel)));
|
||||
("waterLevel", (int)component.WaterLevel)));
|
||||
args.PushMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message",
|
||||
("nutritionLevel", (int) component.NutritionLevel)));
|
||||
("nutritionLevel", (int)component.NutritionLevel)));
|
||||
|
||||
if (component.DrawWarnings)
|
||||
{
|
||||
|
|
@ -299,21 +297,12 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
healthOverride = component.Health;
|
||||
}
|
||||
var packetSeed = component.Seed;
|
||||
if (packetSeed.Sentient)
|
||||
{
|
||||
packetSeed = packetSeed.Clone(); // clone before modifying the seed
|
||||
packetSeed.Sentient = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
packetSeed.Unique = false;
|
||||
}
|
||||
var seed = _botany.SpawnSeedPacket(packetSeed, Transform(args.User).Coordinates, args.User, healthOverride);
|
||||
_randomHelper.RandomOffset(seed, 0.25f);
|
||||
var displayName = Loc.GetString(component.Seed.DisplayName);
|
||||
_popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
|
||||
("seedName", displayName)), args.User);
|
||||
|
||||
|
||||
DoScream(entity.Owner, component.Seed);
|
||||
|
||||
if (_random.Prob(0.3f))
|
||||
|
|
@ -459,7 +448,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
else
|
||||
{
|
||||
if (_random.Prob(0.8f))
|
||||
component.Age += (int) (1 * HydroponicsSpeedMultiplier);
|
||||
component.Age += (int)(1 * HydroponicsSpeedMultiplier);
|
||||
|
||||
component.UpdateSpriteAfterUpdate = true;
|
||||
}
|
||||
|
|
@ -632,12 +621,6 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
else if (component.Age < 0) // Revert back to seed packet!
|
||||
{
|
||||
var packetSeed = component.Seed;
|
||||
if (packetSeed.Sentient)
|
||||
{
|
||||
if (!packetSeed.Unique) // clone if necessary before modifying the seed
|
||||
packetSeed = packetSeed.Clone();
|
||||
packetSeed.Sentient = false; // remove Sentient to avoid ghost role spam
|
||||
}
|
||||
// will put it in the trays hands if it has any, please do not try doing this
|
||||
_botany.SpawnSeedPacket(packetSeed, Transform(uid).Coordinates, uid);
|
||||
RemovePlant(uid, component);
|
||||
|
|
@ -674,14 +657,6 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
|
||||
CheckLevelSanity(uid, component);
|
||||
|
||||
if (component.Seed.Sentient)
|
||||
{
|
||||
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
|
||||
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
||||
ghostRole.RoleName = MetaData(uid).EntityName;
|
||||
ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName));
|
||||
}
|
||||
|
||||
if (component.UpdateSpriteAfterUpdate)
|
||||
UpdateSprite(uid, component);
|
||||
}
|
||||
|
|
@ -911,7 +886,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
if (component.Seed != null)
|
||||
{
|
||||
EnsureUniqueSeed(uid, component);
|
||||
_mutation.MutateSeed(ref component.Seed, severity);
|
||||
_mutation.MutateSeed(uid, ref component.Seed, severity);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -922,19 +897,6 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
|
||||
component.UpdateSpriteAfterUpdate = false;
|
||||
|
||||
if (component.Seed != null && component.Seed.Bioluminescent)
|
||||
{
|
||||
var light = EnsureComp<PointLightComponent>(uid);
|
||||
_pointLight.SetRadius(uid, component.Seed.BioluminescentRadius, light);
|
||||
_pointLight.SetColor(uid, component.Seed.BioluminescentColor, light);
|
||||
_pointLight.SetCastShadows(uid, false, light);
|
||||
Dirty(uid, light);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemComp<PointLightComponent>(uid);
|
||||
}
|
||||
|
||||
if (!TryComp<AppearanceComponent>(uid, out var app))
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,12 +43,6 @@ public sealed class SeedExtractorSystem : EntitySystem
|
|||
var coords = Transform(uid).Coordinates;
|
||||
|
||||
var packetSeed = seed;
|
||||
if (packetSeed.Sentient)
|
||||
{
|
||||
if (!packetSeed.Unique) // clone if necessary before modifying the seed
|
||||
packetSeed = packetSeed.Clone();
|
||||
packetSeed.Sentient = false; // remove Sentient to avoid ghost role spam
|
||||
}
|
||||
if (amount > 1)
|
||||
packetSeed.Unique = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,31 +14,31 @@ public sealed partial class SolutionRegenerationComponent : Component
|
|||
/// <summary>
|
||||
/// The name of the solution to add to.
|
||||
/// </summary>
|
||||
[DataField("solution", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution", required: true)]
|
||||
public string SolutionName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The solution to add reagents to.
|
||||
/// </summary>
|
||||
[DataField("solutionRef")]
|
||||
public Entity<SolutionComponent>? Solution = null;
|
||||
[DataField]
|
||||
public Entity<SolutionComponent>? SolutionRef = null;
|
||||
|
||||
/// <summary>
|
||||
/// The reagent(s) to be regenerated in the solution.
|
||||
/// </summary>
|
||||
[DataField("generated", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(required: true)]
|
||||
public Solution Generated = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to regenerate once.
|
||||
/// </summary>
|
||||
[DataField("duration"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan Duration = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// The time when the next regeneration will occur.
|
||||
/// </summary>
|
||||
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextRegenTime = TimeSpan.FromSeconds(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem
|
|||
|
||||
// timer ignores if its full, it's just a fixed cycle
|
||||
regen.NextRegenTime = _timing.CurTime + regen.Duration;
|
||||
if (_solutionContainer.ResolveSolution((uid, manager), regen.SolutionName, ref regen.Solution, out var solution))
|
||||
if (_solutionContainer.ResolveSolution((uid, manager), regen.SolutionName, ref regen.SolutionRef, out var solution))
|
||||
{
|
||||
var amount = FixedPoint2.Min(solution.AvailableVolume, regen.Generated.Volume);
|
||||
if (amount <= FixedPoint2.Zero)
|
||||
|
|
@ -41,7 +41,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem
|
|||
generated = regen.Generated.Clone().SplitSolution(amount);
|
||||
}
|
||||
|
||||
_solutionContainer.TryAddSolution(regen.Solution.Value, generated);
|
||||
_solutionContainer.TryAddSolution(regen.SolutionRef.Value, generated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,50 @@
|
|||
using Content.Server.Stack;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Construction.Completions
|
||||
namespace Content.Server.Construction.Completions;
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class GivePrototype : IGraphAction
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class GivePrototype : IGraphAction
|
||||
{
|
||||
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Prototype { get; private set; } = string.Empty;
|
||||
[DataField("amount")]
|
||||
public int Amount { get; private set; } = 1;
|
||||
[DataField]
|
||||
public EntProtoId Prototype { get; private set; } = string.Empty;
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
[DataField]
|
||||
public int Amount { get; private set; } = 1;
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Prototype))
|
||||
return;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Prototype))
|
||||
var stackSystem = entityManager.EntitySysManager.GetEntitySystem<StackSystem>();
|
||||
var stacks = stackSystem.SpawnMultiple(Prototype, Amount, userUid ?? uid);
|
||||
|
||||
if (userUid is null || !entityManager.TryGetComponent(userUid, out HandsComponent? handsComp))
|
||||
return;
|
||||
|
||||
var coordinates = entityManager.GetComponent<TransformComponent>(userUid ?? uid).Coordinates;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
|
||||
foreach (var item in stacks)
|
||||
{
|
||||
var stackEnt = entityManager.SpawnEntity(Prototype, coordinates);
|
||||
var stack = entityManager.GetComponent<StackComponent>(stackEnt);
|
||||
entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount(stackEnt, Amount, stack);
|
||||
entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>().PickupOrDrop(userUid, stackEnt);
|
||||
stackSystem.TryMergeToHands(item, userUid.Value, hands: handsComp);
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
var handsSystem = entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>();
|
||||
var handsComp = userUid is not null ? entityManager.GetComponent<HandsComponent>(userUid.Value) : null;
|
||||
for (var i = 0; i < Amount; i++)
|
||||
{
|
||||
for (var i = 0; i < Amount; i++)
|
||||
{
|
||||
var item = entityManager.SpawnEntity(Prototype, coordinates);
|
||||
entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>().PickupOrDrop(userUid, item);
|
||||
}
|
||||
var item = entityManager.SpawnNextToOrDrop(Prototype, userUid ?? uid);
|
||||
handsSystem.PickupOrDrop(userUid, item, handsComp: handsComp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Shared.Coordinates.Helpers;
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Stacks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
|
@ -15,6 +16,7 @@ namespace Content.Server.Engineering.EntitySystems
|
|||
{
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
[Dependency] private readonly TurfSystem _turfSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -36,7 +38,7 @@ namespace Content.Server.Engineering.EntitySystems
|
|||
|
||||
bool IsTileClear()
|
||||
{
|
||||
return tileRef.Tile.IsEmpty == false && !tileRef.IsBlockedTurf(true);
|
||||
return tileRef.Tile.IsEmpty == false && !_turfSystem.IsTileBlocked(tileRef, CollisionGroup.MobMask);
|
||||
}
|
||||
|
||||
if (!IsTileClear())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Makes a mob glow.
|
||||
/// </summary>
|
||||
public sealed partial class Glow : EntityEffect
|
||||
{
|
||||
[DataField]
|
||||
public float Radius = 2f;
|
||||
|
||||
[DataField]
|
||||
public Color Color = Color.Black;
|
||||
|
||||
private static readonly List<Color> Colors = new()
|
||||
{
|
||||
Color.White,
|
||||
Color.Red,
|
||||
Color.Yellow,
|
||||
Color.Green,
|
||||
Color.Blue,
|
||||
Color.Purple,
|
||||
Color.Pink
|
||||
};
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (Color == Color.Black)
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
Color = random.Pick(Colors);
|
||||
}
|
||||
|
||||
var lightSystem = args.EntityManager.System<SharedPointLightSystem>();
|
||||
var light = lightSystem.EnsureLight(args.TargetEntity);
|
||||
lightSystem.SetRadius(args.TargetEntity, Radius, light);
|
||||
lightSystem.SetColor(args.TargetEntity, Color, light);
|
||||
lightSystem.SetCastShadows(args.TargetEntity, false, light); // this is expensive, and botanists make lots of plants
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
using Content.Server.Botany;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class PlantChangeStat : EntityEffect
|
||||
{
|
||||
[DataField]
|
||||
public string TargetValue;
|
||||
|
||||
[DataField]
|
||||
public float MinValue;
|
||||
|
||||
[DataField]
|
||||
public float MaxValue;
|
||||
|
||||
[DataField]
|
||||
public int Steps;
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var plantHolder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
if (plantHolder == null || plantHolder.Seed == null)
|
||||
return;
|
||||
|
||||
var member = plantHolder.Seed.GetType().GetField(TargetValue);
|
||||
var mutationSys = args.EntityManager.System<MutationSystem>();
|
||||
|
||||
if (member == null)
|
||||
{
|
||||
mutationSys.Log.Error(this.GetType().Name + " Error: Member " + TargetValue + " not found on " + plantHolder.GetType().Name + ". Did you misspell it?");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentValObj = member.GetValue(plantHolder.Seed);
|
||||
if (currentValObj == null)
|
||||
return;
|
||||
|
||||
if (member.FieldType == typeof(float))
|
||||
{
|
||||
var floatVal = (float)currentValObj;
|
||||
MutateFloat(ref floatVal, MinValue, MaxValue, Steps);
|
||||
member.SetValue(plantHolder.Seed, floatVal);
|
||||
}
|
||||
else if (member.FieldType == typeof(int))
|
||||
{
|
||||
var intVal = (int)currentValObj;
|
||||
MutateInt(ref intVal, (int)MinValue, (int)MaxValue, Steps);
|
||||
member.SetValue(plantHolder.Seed, intVal);
|
||||
}
|
||||
else if (member.FieldType == typeof(bool))
|
||||
{
|
||||
var boolVal = (bool)currentValObj;
|
||||
boolVal = !boolVal;
|
||||
member.SetValue(plantHolder.Seed, boolVal);
|
||||
}
|
||||
}
|
||||
|
||||
// Mutate reference 'val' between 'min' and 'max' by pretending the value
|
||||
// is representable by a thermometer code with 'bits' number of bits and
|
||||
// randomly flipping some of them.
|
||||
private void MutateFloat(ref float val, float min, float max, int bits)
|
||||
{
|
||||
if (min == max)
|
||||
{
|
||||
val = min;
|
||||
return;
|
||||
}
|
||||
|
||||
// Starting number of bits that are high, between 0 and bits.
|
||||
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
||||
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
||||
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
||||
valInt = Math.Clamp(valInt, 0, bits);
|
||||
|
||||
// Probability that the bit flip increases n.
|
||||
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
|
||||
// In other words, it tends to go to the middle.
|
||||
float probIncrease = 1 - (float)valInt / bits;
|
||||
int valIntMutated;
|
||||
if (Random(probIncrease))
|
||||
{
|
||||
valIntMutated = valInt + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valIntMutated = valInt - 1;
|
||||
}
|
||||
|
||||
// Set value based on mutated thermometer code.
|
||||
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
|
||||
val = valMutated;
|
||||
}
|
||||
|
||||
private void MutateInt(ref int val, int min, int max, int bits)
|
||||
{
|
||||
if (min == max)
|
||||
{
|
||||
val = min;
|
||||
return;
|
||||
}
|
||||
|
||||
// Starting number of bits that are high, between 0 and bits.
|
||||
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
||||
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
||||
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
||||
valInt = Math.Clamp(valInt, 0, bits);
|
||||
|
||||
// Probability that the bit flip increases n.
|
||||
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
|
||||
// In other words, it tends to go to the middle.
|
||||
float probIncrease = 1 - (float)valInt / bits;
|
||||
int valMutated;
|
||||
if (Random(probIncrease))
|
||||
{
|
||||
valMutated = val + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valMutated = val - 1;
|
||||
}
|
||||
|
||||
valMutated = Math.Clamp(valMutated, min, max);
|
||||
val = valMutated;
|
||||
}
|
||||
|
||||
private bool Random(float odds)
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
return random.Prob(odds);
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using Content.Server.Botany;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// changes the chemicals available in a plant's produce
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateChemicals : EntityEffect
|
||||
{
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
|
||||
if (plantholder.Seed == null)
|
||||
return;
|
||||
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var chemicals = plantholder.Seed.Chemicals;
|
||||
var randomChems = prototypeManager.Index<WeightedRandomFillSolutionPrototype>("RandomPickBotanyReagent").Fills;
|
||||
|
||||
// Add a random amount of a random chemical to this set of chemicals
|
||||
if (randomChems != null)
|
||||
{
|
||||
var pick = random.Pick<RandomFillSolution>(randomChems);
|
||||
var chemicalId = random.Pick(pick.Reagents);
|
||||
var amount = random.Next(1, (int)pick.Quantity);
|
||||
var seedChemQuantity = new SeedChemQuantity();
|
||||
if (chemicals.ContainsKey(chemicalId))
|
||||
{
|
||||
seedChemQuantity.Min = chemicals[chemicalId].Min;
|
||||
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
seedChemQuantity.Min = 1;
|
||||
seedChemQuantity.Max = 1 + amount;
|
||||
seedChemQuantity.Inherent = false;
|
||||
}
|
||||
var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
|
||||
seedChemQuantity.PotencyDivisor = potencyDivisor;
|
||||
chemicals[chemicalId] = seedChemQuantity;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// changes the gases that a plant or produce create.
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateExudeGasses : EntityEffect
|
||||
{
|
||||
[DataField]
|
||||
public float MinValue = 0.01f;
|
||||
|
||||
[DataField]
|
||||
public float MaxValue = 0.5f;
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
|
||||
if (plantholder.Seed == null)
|
||||
return;
|
||||
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var gasses = plantholder.Seed.ExudeGasses;
|
||||
|
||||
// Add a random amount of a random gas to this gas dictionary
|
||||
float amount = random.NextFloat(MinValue, MaxValue);
|
||||
Gas gas = random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
||||
if (gasses.ContainsKey(gas))
|
||||
{
|
||||
gasses[gas] += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
gasses.Add(gas, amount);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// changes the gases that a plant or produce consumes.
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateConsumeGasses : EntityEffect
|
||||
{
|
||||
[DataField]
|
||||
public float MinValue = 0.01f;
|
||||
|
||||
[DataField]
|
||||
public float MaxValue = 0.5f;
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
|
||||
if (plantholder.Seed == null)
|
||||
return;
|
||||
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var gasses = plantholder.Seed.ConsumeGasses;
|
||||
|
||||
// Add a random amount of a random gas to this gas dictionary
|
||||
float amount = random.NextFloat(MinValue, MaxValue);
|
||||
Gas gas = random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
||||
if (gasses.ContainsKey(gas))
|
||||
{
|
||||
gasses[gas] += amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
gasses.Add(gas, amount);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using Content.Server.Botany;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Upgrades a plant's harvest type.
|
||||
/// </summary>
|
||||
public sealed partial class PlantMutateHarvest : EntityEffect
|
||||
{
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
|
||||
if (plantholder.Seed == null)
|
||||
return;
|
||||
|
||||
if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat)
|
||||
plantholder.Seed.HarvestRepeat = HarvestType.Repeat;
|
||||
else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat)
|
||||
plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest;
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using Content.Server.Botany;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Changes a plant into one of the species its able to mutate into.
|
||||
/// </summary>
|
||||
public sealed partial class PlantSpeciesChange : EntityEffect
|
||||
{
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
|
||||
|
||||
if (plantholder.Seed == null)
|
||||
return;
|
||||
|
||||
if (plantholder.Seed.MutationPrototypes.Count == 0)
|
||||
return;
|
||||
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var targetProto = random.Pick(plantholder.Seed.MutationPrototypes);
|
||||
prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed);
|
||||
|
||||
if (protoSeed == null)
|
||||
{
|
||||
Log.Error($"Seed prototype could not be found: {targetProto}!");
|
||||
return;
|
||||
}
|
||||
|
||||
plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed);
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.StepTrigger.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Makes a mob slippery.
|
||||
/// </summary>
|
||||
public sealed partial class Slipify : EntityEffect
|
||||
{
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var fixtureSystem = args.EntityManager.System<FixtureSystem>();
|
||||
var colWakeSystem = args.EntityManager.System<CollisionWakeSystem>();
|
||||
var slippery = args.EntityManager.EnsureComponent<SlipperyComponent>(args.TargetEntity);
|
||||
args.EntityManager.Dirty(args.TargetEntity, slippery);
|
||||
args.EntityManager.EnsureComponent<StepTriggerComponent>(args.TargetEntity);
|
||||
// Need a fixture with a slip layer in order to actually do the slipping
|
||||
var fixtures = args.EntityManager.EnsureComponent<FixturesComponent>(args.TargetEntity);
|
||||
var body = args.EntityManager.EnsureComponent<PhysicsComponent>(args.TargetEntity);
|
||||
var shape = fixtures.Fixtures["fix1"].Shape;
|
||||
fixtureSystem.TryCreateFixture(args.TargetEntity, shape, "slips", 1, false, (int)CollisionGroup.SlipLayer, manager: fixtures, body: body);
|
||||
// Need to disable collision wake so that mobs can collide with and slip on it
|
||||
var collisionWake = args.EntityManager.EnsureComponent<CollisionWakeComponent>(args.TargetEntity);
|
||||
colWakeSystem.SetEnabled(args.TargetEntity, false, collisionWake);
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -202,6 +202,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args)
|
||||
{
|
||||
if (!TryComp<SubdermalImplantComponent>(uid, out var implanted))
|
||||
|
|
@ -230,7 +231,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||
private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard))
|
||||
Trigger(uid);
|
||||
Trigger(uid, args.OtherEntity);
|
||||
}
|
||||
|
||||
private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ public sealed class GravityGeneratorSystem : EntitySystem
|
|||
private void OnActivated(Entity<GravityGeneratorComponent> ent, ref ChargedMachineActivatedEvent args)
|
||||
{
|
||||
ent.Comp.GravityActive = true;
|
||||
if (TryComp<TransformComponent>(ent, out var xform) &&
|
||||
TryComp(xform.ParentUid, out GravityComponent? gravity))
|
||||
|
||||
var xform = Transform(ent);
|
||||
|
||||
if (TryComp(xform.ParentUid, out GravityComponent? gravity))
|
||||
{
|
||||
_gravitySystem.EnableGravity(xform.ParentUid, gravity);
|
||||
}
|
||||
|
|
@ -46,8 +48,10 @@ public sealed class GravityGeneratorSystem : EntitySystem
|
|||
private void OnDeactivated(Entity<GravityGeneratorComponent> ent, ref ChargedMachineDeactivatedEvent args)
|
||||
{
|
||||
ent.Comp.GravityActive = false;
|
||||
if (TryComp<TransformComponent>(ent, out var xform) &&
|
||||
TryComp(xform.ParentUid, out GravityComponent? gravity))
|
||||
|
||||
var xform = Transform(ent);
|
||||
|
||||
if (TryComp(xform.ParentUid, out GravityComponent? gravity))
|
||||
{
|
||||
_gravitySystem.RefreshGravity(xform.ParentUid, gravity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,12 @@ namespace Content.Server.Guardian
|
|||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (_container.IsEntityInContainer(uid))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("guardian-inside-container"), uid, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.HostedGuardian != null)
|
||||
ToggleGuardian(uid, component);
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ public sealed class IdentitySystem : SharedIdentitySystem
|
|||
SubscribeLocalEvent<IdentityComponent, DidUnequipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, DidUnequipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, WearerMaskToggledEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
|
||||
SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Content.Server.Discord;
|
|||
using Content.Server.EUI;
|
||||
using Content.Server.GhostKick;
|
||||
using Content.Server.Info;
|
||||
using Content.Server.Mapping;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.MoMMI;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
|
|
@ -67,6 +68,7 @@ namespace Content.Server.IoC
|
|||
IoCManager.Register<ServerApi>();
|
||||
IoCManager.Register<JobWhitelistManager>();
|
||||
IoCManager.Register<PlayerRateLimitManager>();
|
||||
IoCManager.Register<MappingManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||
SetAppearance(ent.Owner, MicrowaveVisualState.Cooking, microwaveComponent);
|
||||
|
||||
microwaveComponent.PlayingStream =
|
||||
_audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5)).Value.Entity;
|
||||
_audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5))?.Entity;
|
||||
}
|
||||
|
||||
private void OnCookStop(Entity<ActiveMicrowaveComponent> ent, ref ComponentShutdown args)
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||
active.Program = program;
|
||||
|
||||
reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid,
|
||||
AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier)).Value.Entity; //slightly higher pitched
|
||||
AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier))?.Entity; //slightly higher pitched
|
||||
_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
|
||||
new ReagentGrinderWorkStartedMessage(program));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Content.Shared.Storage;
|
|||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
|
|
@ -72,12 +73,17 @@ public sealed class SharpSystem : EntitySystem
|
|||
if (!sharp.Butchering.Add(target))
|
||||
return false;
|
||||
|
||||
// if the user isn't the entity with the sharp component,
|
||||
// they will need to be holding something with their hands, so we set needHand to true
|
||||
// so that the doafter can be interrupted if they drop the item in their hands
|
||||
var needHand = user != knife;
|
||||
|
||||
var doAfter =
|
||||
new DoAfterArgs(EntityManager, user, sharp.ButcherDelayModifier * butcher.ButcherDelay, new SharpDoAfterEvent(), knife, target: target, used: knife)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = true,
|
||||
NeedHand = needHand,
|
||||
};
|
||||
_doAfterSystem.TryStartDoAfter(doAfter);
|
||||
return true;
|
||||
|
|
@ -136,13 +142,20 @@ public sealed class SharpSystem : EntitySystem
|
|||
|
||||
private void OnGetInteractionVerbs(EntityUid uid, ButcherableComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (component.Type != ButcheringType.Knife || args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
if (component.Type != ButcheringType.Knife || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
bool disabled = false;
|
||||
// if the user has no hands, don't show them the verb if they have no SharpComponent either
|
||||
if (!TryComp<SharpComponent>(args.User, out var userSharpComp) && args.Hands == null)
|
||||
return;
|
||||
|
||||
var disabled = false;
|
||||
string? message = null;
|
||||
|
||||
if (!HasComp<SharpComponent>(args.Using))
|
||||
// if the user has hands
|
||||
// and the item they're holding doesn't have the SharpComponent
|
||||
// disable the verb
|
||||
if (!TryComp<SharpComponent>(args.Using, out var usingSharpComp) && args.Hands != null)
|
||||
{
|
||||
disabled = true;
|
||||
message = Loc.GetString("butcherable-need-knife",
|
||||
|
|
@ -150,9 +163,9 @@ public sealed class SharpSystem : EntitySystem
|
|||
}
|
||||
else if (_containerSystem.IsEntityInContainer(uid))
|
||||
{
|
||||
disabled = true;
|
||||
message = Loc.GetString("butcherable-not-in-container",
|
||||
("target", uid));
|
||||
disabled = true;
|
||||
}
|
||||
else if (TryComp<MobStateComponent>(uid, out var state) && !_mobStateSystem.IsDead(uid, state))
|
||||
{
|
||||
|
|
@ -160,12 +173,20 @@ public sealed class SharpSystem : EntitySystem
|
|||
message = Loc.GetString("butcherable-mob-isnt-dead");
|
||||
}
|
||||
|
||||
// set the object doing the butchering to the item in the user's hands or to the user themselves
|
||||
// if either has the SharpComponent
|
||||
EntityUid sharpObject = default;
|
||||
if (usingSharpComp != null)
|
||||
sharpObject = args.Using!.Value;
|
||||
else if (userSharpComp != null)
|
||||
sharpObject = args.User;
|
||||
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
if (!disabled)
|
||||
TryStartButcherDoafter(args.Using!.Value, args.Target, args.User);
|
||||
TryStartButcherDoafter(sharpObject, args.Target, args.User);
|
||||
},
|
||||
Message = message,
|
||||
Disabled = disabled,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
using System.IO;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mapping;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Server.Mapping;
|
||||
|
||||
public sealed class MappingManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _admin = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IServerNetManager _net = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
private ZStdCompressionContext _zstd = default!;
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
_net.RegisterNetMessage<MappingSaveMapMessage>(OnMappingSaveMap);
|
||||
_net.RegisterNetMessage<MappingSaveMapErrorMessage>();
|
||||
_net.RegisterNetMessage<MappingMapDataMessage>();
|
||||
|
||||
_sawmill = _log.GetSawmill("mapping");
|
||||
_zstd = new ZStdCompressionContext();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnMappingSaveMap(MappingSaveMapMessage message)
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
try
|
||||
{
|
||||
if (!_players.TryGetSessionByChannel(message.MsgChannel, out var session) ||
|
||||
!_admin.IsAdmin(session, true) ||
|
||||
!_admin.HasAdminFlag(session, AdminFlags.Host) ||
|
||||
session.AttachedEntity is not { } player)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = _systems.GetEntitySystem<TransformSystem>().GetMapCoordinates(player).MapId;
|
||||
var mapEntity = _map.GetMapEntityIdOrThrow(mapId);
|
||||
var data = _systems.GetEntitySystem<MapLoaderSystem>().GetSaveData(mapEntity);
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
var stream = new YamlStream { document };
|
||||
var writer = new StringWriter();
|
||||
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||
|
||||
var msg = new MappingMapDataMessage()
|
||||
{
|
||||
Context = _zstd,
|
||||
Yml = writer.ToString()
|
||||
};
|
||||
_net.ServerSendMessage(msg, message.MsgChannel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Error saving map in mapping mode:\n{e}");
|
||||
var msg = new MappingSaveMapErrorMessage();
|
||||
_net.ServerSendMessage(msg, message.MsgChannel);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ public sealed class MechGrabberSystem : EntitySystem
|
|||
return;
|
||||
|
||||
args.Handled = true;
|
||||
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid).Value.Entity;
|
||||
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid)?.Entity;
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.GrabDelay, new GrabberDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnMove = true
|
||||
|
|
|
|||
|
|
@ -1,31 +1,22 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.PDA;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Mind.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.VarEdit)]
|
||||
public sealed class RenameCommand : IConsoleCommand
|
||||
public sealed class RenameCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
|
||||
public string Command => "rename";
|
||||
public string Description => "Renames an entity and its cloner entries, ID cards, and PDAs.";
|
||||
public string Help => "rename <Username|EntityUid> <New character name>";
|
||||
public override string Command => "rename";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
|
|
@ -36,69 +27,14 @@ public sealed class RenameCommand : IConsoleCommand
|
|||
var name = args[1];
|
||||
if (name.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||
{
|
||||
shell.WriteLine("Name is too long.");
|
||||
shell.WriteLine(Loc.GetString("cmd-rename-too-long"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseUid(args[0], shell, _entManager, out var entityUid))
|
||||
return;
|
||||
|
||||
// Metadata
|
||||
var metadata = _entManager.GetComponent<MetaDataComponent>(entityUid.Value);
|
||||
var oldName = metadata.EntityName;
|
||||
_entManager.System<MetaDataSystem>().SetEntityName(entityUid.Value, name, metadata);
|
||||
|
||||
var minds = _entManager.System<SharedMindSystem>();
|
||||
|
||||
if (minds.TryGetMind(entityUid.Value, out var mindId, out var mind))
|
||||
{
|
||||
// Mind
|
||||
mind.CharacterName = name;
|
||||
_entManager.Dirty(mindId, mind);
|
||||
}
|
||||
|
||||
// Id Cards
|
||||
if (_entManager.TrySystem<IdCardSystem>(out var idCardSystem))
|
||||
{
|
||||
if (idCardSystem.TryFindIdCard(entityUid.Value, out var idCard))
|
||||
{
|
||||
idCardSystem.TryChangeFullName(idCard, name, idCard);
|
||||
|
||||
// Records
|
||||
// This is done here because ID cards are linked to station records
|
||||
if (_entManager.TrySystem<StationRecordsSystem>(out var recordsSystem)
|
||||
&& _entManager.TryGetComponent(idCard, out StationRecordKeyStorageComponent? keyStorage)
|
||||
&& keyStorage.Key is {} key)
|
||||
{
|
||||
if (recordsSystem.TryGetRecord<GeneralStationRecord>(key, out var generalRecord))
|
||||
{
|
||||
generalRecord.Name = name;
|
||||
}
|
||||
|
||||
recordsSystem.Synchronize(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PDAs
|
||||
if (_entManager.TrySystem<PdaSystem>(out var pdaSystem))
|
||||
{
|
||||
var query = _entManager.EntityQueryEnumerator<PdaComponent>();
|
||||
while (query.MoveNext(out var uid, out var pda))
|
||||
{
|
||||
if (pda.OwnerName == oldName)
|
||||
{
|
||||
pdaSystem.SetOwner(uid, pda, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Admin Overlay
|
||||
if (_entManager.TrySystem<AdminSystem>(out var adminSystem)
|
||||
&& _entManager.TryGetComponent<ActorComponent>(entityUid, out var actorComp))
|
||||
{
|
||||
adminSystem.UpdatePlayerList(actorComp.PlayerSession);
|
||||
}
|
||||
_metaSystem.SetEntityName(entityUid.Value, name);
|
||||
}
|
||||
|
||||
private bool TryParseUid(string str, IConsoleShell shell,
|
||||
|
|
@ -114,9 +50,9 @@ public sealed class RenameCommand : IConsoleCommand
|
|||
}
|
||||
|
||||
if (session == null)
|
||||
shell.WriteError("Can't find username/uid: " + str);
|
||||
shell.WriteError(Loc.GetString("cmd-rename-not-found", ("target", str)));
|
||||
else
|
||||
shell.WriteError(str + " does not have an entity.");
|
||||
shell.WriteError(Loc.GetString("cmd-rename-no-entity", ("target", str)));
|
||||
|
||||
entityUid = EntityUid.Invalid;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Components.Targets;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
|
|
@ -20,11 +21,14 @@ public sealed class StealConditionSystem : EntitySystem
|
|||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
private EntityQuery<ContainerManagerComponent> _containerQuery;
|
||||
|
||||
private HashSet<Entity<TransformComponent>> _nearestEnts = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -72,14 +76,15 @@ public sealed class StealConditionSystem : EntitySystem
|
|||
private void OnAfterAssign(Entity<StealConditionComponent> condition, ref ObjectiveAfterAssignEvent args)
|
||||
{
|
||||
var group = _proto.Index(condition.Comp.StealGroup);
|
||||
string localizedName = Loc.GetString(group.Name);
|
||||
|
||||
var title =condition.Comp.OwnerText == null
|
||||
? Loc.GetString(condition.Comp.ObjectiveNoOwnerText, ("itemName", group.Name))
|
||||
: Loc.GetString(condition.Comp.ObjectiveText, ("owner", Loc.GetString(condition.Comp.OwnerText)), ("itemName", group.Name));
|
||||
? Loc.GetString(condition.Comp.ObjectiveNoOwnerText, ("itemName", localizedName))
|
||||
: Loc.GetString(condition.Comp.ObjectiveText, ("owner", Loc.GetString(condition.Comp.OwnerText)), ("itemName", localizedName));
|
||||
|
||||
var description = condition.Comp.CollectionSize > 1
|
||||
? Loc.GetString(condition.Comp.DescriptionMultiplyText, ("itemName", group.Name), ("count", condition.Comp.CollectionSize))
|
||||
: Loc.GetString(condition.Comp.DescriptionText, ("itemName", group.Name));
|
||||
? Loc.GetString(condition.Comp.DescriptionMultiplyText, ("itemName", localizedName), ("count", condition.Comp.CollectionSize))
|
||||
: Loc.GetString(condition.Comp.DescriptionText, ("itemName", localizedName));
|
||||
|
||||
_metaData.SetEntityName(condition.Owner, title, args.Meta);
|
||||
_metaData.SetEntityDescription(condition.Owner, description, args.Meta);
|
||||
|
|
@ -101,15 +106,19 @@ public sealed class StealConditionSystem : EntitySystem
|
|||
//check stealAreas
|
||||
if (condition.CheckStealAreas)
|
||||
{
|
||||
var areasQuery = AllEntityQuery<StealAreaComponent>();
|
||||
while (areasQuery.MoveNext(out var uid, out var area))
|
||||
var areasQuery = AllEntityQuery<StealAreaComponent, TransformComponent>();
|
||||
while (areasQuery.MoveNext(out var uid, out var area, out var xform))
|
||||
{
|
||||
if (!area.Owners.Contains(mind.Owner))
|
||||
continue;
|
||||
|
||||
var nearestEnt = _lookup.GetEntitiesInRange(uid, area.Range);
|
||||
foreach (var ent in nearestEnt)
|
||||
_nearestEnts.Clear();
|
||||
_lookup.GetEntitiesInRange<TransformComponent>(xform.Coordinates, area.Range, _nearestEnts);
|
||||
foreach (var ent in _nearestEnts)
|
||||
{
|
||||
if (!_interaction.InRangeUnobstructed((uid, xform), (ent, ent.Comp), range: area.Range))
|
||||
continue;
|
||||
|
||||
CheckEntity(ent, condition, ref containerStack, ref count);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,9 +55,23 @@ namespace Content.Server.PDA
|
|||
SubscribeLocalEvent<PdaComponent, CartridgeLoaderNotificationSentEvent>(OnNotification);
|
||||
|
||||
SubscribeLocalEvent<StationRenamedEvent>(OnStationRenamed);
|
||||
SubscribeLocalEvent<EntityRenamedEvent>(OnEntityRenamed);
|
||||
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
|
||||
}
|
||||
|
||||
private void OnEntityRenamed(ref EntityRenamedEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<PdaComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.PdaOwner == ev.Uid)
|
||||
{
|
||||
SetOwner(uid, comp, ev.Uid, ev.NewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnComponentInit(EntityUid uid, PdaComponent pda, ComponentInit args)
|
||||
{
|
||||
base.OnComponentInit(uid, pda, args);
|
||||
|
|
@ -94,9 +108,10 @@ namespace Content.Server.PDA
|
|||
UpdatePdaUi(uid, pda);
|
||||
}
|
||||
|
||||
public void SetOwner(EntityUid uid, PdaComponent pda, string ownerName)
|
||||
public void SetOwner(EntityUid uid, PdaComponent pda, EntityUid owner, string ownerName)
|
||||
{
|
||||
pda.OwnerName = ownerName;
|
||||
pda.PdaOwner = owner;
|
||||
UpdatePdaUi(uid, pda);
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +127,7 @@ namespace Content.Server.PDA
|
|||
|
||||
private void UpdateAllPdaUisOnStation()
|
||||
{
|
||||
var query = EntityQueryEnumerator<PdaComponent>();
|
||||
var query = AllEntityQuery<PdaComponent>();
|
||||
while (query.MoveNext(out var ent, out var comp))
|
||||
{
|
||||
UpdatePdaUi(ent, comp);
|
||||
|
|
|
|||
|
|
@ -102,10 +102,6 @@ public sealed class TegSystem : EntitySystem
|
|||
|
||||
private void GeneratorUpdate(EntityUid uid, TegGeneratorComponent component, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var tegGroup = GetNodeGroup(uid);
|
||||
if (tegGroup is not { IsFullyBuilt: true })
|
||||
return;
|
||||
|
||||
var supplier = Comp<PowerSupplierComponent>(uid);
|
||||
var powerReceiver = Comp<ApcPowerReceiverComponent>(uid);
|
||||
if (!powerReceiver.Powered)
|
||||
|
|
@ -114,6 +110,10 @@ public sealed class TegSystem : EntitySystem
|
|||
return;
|
||||
}
|
||||
|
||||
var tegGroup = GetNodeGroup(uid);
|
||||
if (tegGroup is not { IsFullyBuilt: true })
|
||||
return;
|
||||
|
||||
var circA = tegGroup.CirculatorA!.Owner;
|
||||
var circB = tegGroup.CirculatorB!.Owner;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ using Content.Server.Administration.Logs;
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Respawn;
|
||||
using Content.Shared.Station.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Respawn;
|
||||
|
||||
|
|
@ -179,7 +178,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
|
|||
|
||||
foreach (var newTileRef in grid.GetTilesIntersecting(circle))
|
||||
{
|
||||
if (newTileRef.IsSpace(_tileDefinitionManager) || newTileRef.IsBlockedTurf(true) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget))
|
||||
if (newTileRef.IsSpace(_tileDefinitionManager) || _turf.IsTileBlocked(newTileRef, CollisionGroup.MobMask) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget))
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ namespace Content.Server.RoundEnd
|
|||
return _countdownTokenSource != null;
|
||||
}
|
||||
|
||||
public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station")
|
||||
public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "round-end-system-shuttle-sender-announcement")
|
||||
{
|
||||
var duration = DefaultCountdownDuration;
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ namespace Content.Server.RoundEnd
|
|||
RequestRoundEnd(duration, requester, checkCooldown, text, name);
|
||||
}
|
||||
|
||||
public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station")
|
||||
public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "round-end-system-shuttle-sender-announcement")
|
||||
{
|
||||
if (_gameTicker.RunLevel != GameRunLevel.InRound)
|
||||
return;
|
||||
|
|
@ -183,7 +183,7 @@ namespace Content.Server.RoundEnd
|
|||
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString(text,
|
||||
("time", time),
|
||||
("units", Loc.GetString(units))),
|
||||
name,
|
||||
Loc.GetString(name),
|
||||
false,
|
||||
null,
|
||||
Color.Gold);
|
||||
|
|
|
|||
|
|
@ -154,8 +154,8 @@ public sealed partial class SalvageSystem
|
|||
}
|
||||
else if (comp.Stream == null && remaining < audioLength)
|
||||
{
|
||||
var audio = _audio.PlayPvs(comp.Sound, uid).Value;
|
||||
comp.Stream = audio.Entity;
|
||||
var audio = _audio.PlayPvs(comp.Sound, uid);
|
||||
comp.Stream = audio?.Entity;
|
||||
_audio.SetMapAudio(audio);
|
||||
comp.Stage = ExpeditionStage.MusicCountdown;
|
||||
Dirty(uid, comp);
|
||||
|
|
|
|||
|
|
@ -397,7 +397,8 @@ public sealed partial class ShuttleSystem
|
|||
new EntityCoordinates(fromMapUid.Value, _mapSystem.GetGridPosition(entity.Owner)), true, startupAudio.Params);
|
||||
|
||||
_audio.SetPlaybackPosition(clippedAudio, entity.Comp1.StartupTime);
|
||||
clippedAudio.Value.Component.Flags |= AudioFlags.NoOcclusion;
|
||||
if (clippedAudio != null)
|
||||
clippedAudio.Value.Component.Flags |= AudioFlags.NoOcclusion;
|
||||
}
|
||||
|
||||
// Offset the start by buffer range just to avoid overlap.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue