diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a9f7444c05..8c0ef53d07 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -40,21 +40,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) @@ -67,8 +56,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 diff --git a/Content.Client/Actions/UI/ActionAlertTooltip.cs b/Content.Client/Actions/UI/ActionAlertTooltip.cs index f805f6643d..2425cdefb9 100644 --- a/Content.Client/Actions/UI/ActionAlertTooltip.cs +++ b/Content.Client/Actions/UI/ActionAlertTooltip.cs @@ -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); diff --git a/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs b/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs index 7cb32b43df..615f1434df 100644 --- a/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs +++ b/Content.Client/Administration/UI/SetOutfit/SetOutfitMenu.xaml.cs @@ -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 GetPrototypes() + { + // Filter out any StartingGearPrototypes that belong to loadouts + var loadouts = _prototypeManager.EnumeratePrototypes(); + var loadoutGears = loadouts.Select(l => l.StartingGear); + return _prototypeManager.EnumeratePrototypes() + .Where(p => !loadoutGears.Contains(p.ID)); + } + private void PopulateList() { - foreach (var gear in _prototypeManager.EnumeratePrototypes()) + 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()) + foreach (var gear in GetPrototypes()) { if (!string.IsNullOrEmpty(filter) && gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant())) diff --git a/Content.Client/Anomaly/AnomalySystem.cs b/Content.Client/Anomaly/AnomalySystem.cs index c93f0ce949..28c015f302 100644 --- a/Content.Client/Anomaly/AnomalySystem.cs +++ b/Content.Client/Anomaly/AnomalySystem.cs @@ -20,8 +20,9 @@ public sealed class AnomalySystem : SharedAnomalySystem SubscribeLocalEvent(OnAppearanceChanged); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnAnimationComplete); - } + SubscribeLocalEvent(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 ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + sprite.Scale = Vector2.One; + sprite.Color = sprite.Color.WithAlpha(1f); + } } diff --git a/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs new file mode 100644 index 0000000000..efb1a8d46e --- /dev/null +++ b/Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs @@ -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(OnAfterHandleState); + SubscribeLocalEvent(OnCompShutdown); + } + + private void OnAfterHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(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(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 ent, ref ComponentShutdown args) + { + if (!TryComp(ent, out var sprite)) + return; + + var index = sprite.LayerMapGet(ent.Comp.LayerMap); + sprite.LayerSetVisible(index, false); + } +} diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs index ca6336b91b..b525747aa9 100644 --- a/Content.Client/Audio/AmbientSoundSystem.cs +++ b/Content.Client/Audio/AmbientSoundSystem.cs @@ -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++; diff --git a/Content.Client/Audio/ClientGlobalSoundSystem.cs b/Content.Client/Audio/ClientGlobalSoundSystem.cs index 7c77865f74..50c3971d95 100644 --- a/Content.Client/Audio/ClientGlobalSoundSystem.cs +++ b/Content.Client/Audio/ClientGlobalSoundSystem.cs @@ -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) diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs index d60c978ccf..bf7ab26cba 100644 --- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs +++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs @@ -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); } diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs index 992ef03371..0d0f6138f3 100644 --- a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs +++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs @@ -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!", diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs new file mode 100644 index 0000000000..3c97b8b37d --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs @@ -0,0 +1,30 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.UserInterface; + +namespace Content.Client.CartridgeLoader.Cartridges; + +public sealed partial class WantedListUi : UIFragment +{ + private WantedListUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new WantedListUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + switch (state) + { + case WantedListUiState cast: + _fragment?.UpdateState(cast.Records); + break; + } + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs new file mode 100644 index 0000000000..4137f6c2af --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs @@ -0,0 +1,240 @@ +using System.Linq; +using Content.Client.UserInterface.Controls; +using Content.Shared.CriminalRecords.Systems; +using Content.Shared.Security; +using Content.Shared.StatusIcon; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class WantedListUiFragment : BoxContainer +{ + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private readonly SpriteSystem _spriteSystem; + + private string? _selectedTargetName; + private List _wantedRecords = new(); + + public WantedListUiFragment() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _spriteSystem = _entitySystem.GetEntitySystem(); + + SearchBar.OnTextChanged += OnSearchBarTextChanged; + } + + private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args) + { + var found = !String.IsNullOrWhiteSpace(args.Text) + ? _wantedRecords.FindAll(r => + r.TargetInfo.Name.Contains(args.Text) || + r.Status.ToString().Contains(args.Text, StringComparison.OrdinalIgnoreCase)) + : _wantedRecords; + + UpdateState(found, false); + } + + public void UpdateState(List records, bool refresh = true) + { + if (records.Count == 0) + { + NoRecords.Visible = true; + RecordsList.Visible = false; + RecordUnselected.Visible = false; + PersonContainer.Visible = false; + + _selectedTargetName = null; + if (refresh) + _wantedRecords.Clear(); + + RecordsList.PopulateList(new List()); + + return; + } + + NoRecords.Visible = false; + RecordsList.Visible = true; + RecordUnselected.Visible = true; + PersonContainer.Visible = false; + + var dataList = records.Select(r => new StatusListData(r)).ToList(); + + RecordsList.GenerateItem = GenerateItem; + RecordsList.ItemPressed = OnItemSelected; + RecordsList.PopulateList(dataList); + + if (refresh) + _wantedRecords = records; + } + + private void OnItemSelected(BaseButton.ButtonEventArgs args, ListData data) + { + if (data is not StatusListData(var record)) + return; + + FormattedMessage GetLoc(string fluentId, params (string,object)[] args) + { + var msg = new FormattedMessage(); + var fluent = Loc.GetString(fluentId, args); + msg.AddMarkupPermissive(fluent); + return msg; + } + + // Set personal info + PersonName.Text = record.TargetInfo.Name; + TargetAge.SetMessage(GetLoc( + "wanted-list-age-label", + ("age", record.TargetInfo.Age) + )); + TargetJob.SetMessage(GetLoc( + "wanted-list-job-label", + ("job", record.TargetInfo.JobTitle.ToLower()) + )); + TargetSpecies.SetMessage(GetLoc( + "wanted-list-species-label", + ("species", record.TargetInfo.Species.ToLower()) + )); + TargetGender.SetMessage(GetLoc( + "wanted-list-gender-label", + ("gender", record.TargetInfo.Gender) + )); + + // Set reason + WantedReason.SetMessage(GetLoc( + "wanted-list-reason-label", + ("reason", record.Reason ?? Loc.GetString("wanted-list-unknown-reason-label")) + )); + + // Set status + PersonState.SetMessage(GetLoc( + "wanted-list-status-label", + ("status", record.Status.ToString().ToLower()) + )); + + // Set initiator + InitiatorName.SetMessage(GetLoc( + "wanted-list-initiator-label", + ("initiator", record.Initiator ?? Loc.GetString("wanted-list-unknown-initiator-label")) + )); + + // History table + // Clear table if it exists + HistoryTable.RemoveAllChildren(); + + HistoryTable.AddChild(new Label() + { + Text = Loc.GetString("wanted-list-history-table-time-col"), + StyleClasses = { "LabelSmall" }, + HorizontalAlignment = HAlignment.Center, + }); + HistoryTable.AddChild(new Label() + { + Text = Loc.GetString("wanted-list-history-table-reason-col"), + StyleClasses = { "LabelSmall" }, + HorizontalAlignment = HAlignment.Center, + HorizontalExpand = true, + }); + + HistoryTable.AddChild(new Label() + { + Text = Loc.GetString("wanted-list-history-table-initiator-col"), + StyleClasses = { "LabelSmall" }, + HorizontalAlignment = HAlignment.Center, + }); + + if (record.History.Count > 0) + { + HistoryTable.Visible = true; + + foreach (var history in record.History.OrderByDescending(h => h.AddTime)) + { + HistoryTable.AddChild(new Label() + { + Text = $"{history.AddTime.Hours:00}:{history.AddTime.Minutes:00}:{history.AddTime.Seconds:00}", + StyleClasses = { "LabelSmall" }, + VerticalAlignment = VAlignment.Top, + }); + + HistoryTable.AddChild(new RichTextLabel() + { + Text = $"[color=white]{history.Crime}[/color]", + HorizontalExpand = true, + VerticalAlignment = VAlignment.Top, + StyleClasses = { "LabelSubText" }, + Margin = new(10f, 0f), + }); + + HistoryTable.AddChild(new RichTextLabel() + { + Text = $"[color=white]{history.InitiatorName}[/color]", + StyleClasses = { "LabelSubText" }, + VerticalAlignment = VAlignment.Top, + }); + } + } + + RecordUnselected.Visible = false; + PersonContainer.Visible = true; + + // Save selected item + _selectedTargetName = record.TargetInfo.Name; + } + + private void GenerateItem(ListData data, ListContainerButton button) + { + if (data is not StatusListData(var record)) + return; + + var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal, HorizontalExpand = true }; + var label = new Label() { Text = record.TargetInfo.Name }; + var rect = new TextureRect() + { + TextureScale = new(2.2f), + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Margin = new(0f, 0f, 6f, 0f), + }; + + if (record.Status is not SecurityStatus.None) + { + var proto = "SecurityIcon" + record.Status switch + { + SecurityStatus.Detained => "Incarcerated", + _ => record.Status.ToString(), + }; + + if (_prototypeManager.TryIndex(proto, out var prototype)) + { + rect.Texture = _spriteSystem.Frame0(prototype.Icon); + } + } + + box.AddChild(rect); + box.AddChild(label); + button.AddChild(box); + button.AddStyleClass(ListContainer.StyleClassListContainerButton); + + if (record.TargetInfo.Name.Equals(_selectedTargetName)) + { + button.Pressed = true; + // For some reason the event is not called when `Pressed` changed, call it manually. + OnItemSelected( + new(button, new(new(), BoundKeyState.Down, new(), false, new(), new())), + data); + } + } +} + +internal record StatusListData(WantedRecord Record) : ListData; diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml new file mode 100644 index 0000000000..7b5d116ad7 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml @@ -0,0 +1,50 @@ + + + + + + + diff --git a/Content.Client/Explosion/ExplosionSystem.cs b/Content.Client/Explosion/ExplosionSystem.cs index a2ed2d50e0..692782ded4 100644 --- a/Content.Client/Explosion/ExplosionSystem.cs +++ b/Content.Client/Explosion/ExplosionSystem.cs @@ -2,7 +2,4 @@ using Content.Shared.Explosion.EntitySystems; namespace Content.Client.Explosion.EntitySystems; -public sealed class ExplosionSystem : SharedExplosionSystem -{ - -} +public sealed class ExplosionSystem : SharedExplosionSystem; diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs index 046be2aa62..8e41c382dd 100644 --- a/Content.Client/Flash/FlashOverlay.cs +++ b/Content.Client/Flash/FlashOverlay.cs @@ -16,6 +16,7 @@ namespace Content.Client.Flash [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IGameTiming _timing = default!; + private readonly SharedFlashSystem _flash; private readonly StatusEffectsSystem _statusSys; public override OverlaySpace Space => OverlaySpace.WorldSpace; @@ -27,6 +28,7 @@ namespace Content.Client.Flash { IoCManager.InjectDependencies(this); _shader = _prototypeManager.Index("FlashedEffect").InstanceUnique(); + _flash = _entityManager.System(); _statusSys = _entityManager.System(); } @@ -41,7 +43,7 @@ namespace Content.Client.Flash || !_entityManager.TryGetComponent(playerEntity, out var status)) return; - if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status)) + if (!_statusSys.TryGetTime(playerEntity.Value, _flash.FlashedKey, out var time, status)) return; var curTime = _timing.CurTime; diff --git a/Content.Client/Mapping/MappingScreen.xaml b/Content.Client/Mapping/MappingScreen.xaml index b641360847..9cc3e734f0 100644 --- a/Content.Client/Mapping/MappingScreen.xaml +++ b/Content.Client/Mapping/MappingScreen.xaml @@ -78,6 +78,7 @@ ToolTip="Pick (Hold 5)" /> + diff --git a/Content.Client/Mapping/MappingScreen.xaml.cs b/Content.Client/Mapping/MappingScreen.xaml.cs index b2ad2fd83f..46c0e51fad 100644 --- a/Content.Client/Mapping/MappingScreen.xaml.cs +++ b/Content.Client/Mapping/MappingScreen.xaml.cs @@ -96,6 +96,22 @@ public sealed partial class MappingScreen : InGameScreen 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) diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml index 660f2e5e11..dd40749d33 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml @@ -15,6 +15,9 @@ + + 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); } } diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index 0bd75003a2..ccd36c35e8 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -695,6 +695,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().Class(ContextMenuPopup.StyleClassContextMenuPopup) .Prop(PanelContainer.StylePropertyPanel, contextMenuBackground), diff --git a/Content.Client/Traits/ParacusiaSystem.cs b/Content.Client/Traits/ParacusiaSystem.cs index 3789f24cb0..af4d8ef278 100644 --- a/Content.Client/Traits/ParacusiaSystem.cs +++ b/Content.Client/Traits/ParacusiaSystem.cs @@ -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; } } diff --git a/Content.Client/Weather/WeatherSystem.cs b/Content.Client/Weather/WeatherSystem.cs index a0e8a44f40..975831392c 100644 --- a/Content.Client/Weather/WeatherSystem.cs +++ b/Content.Client/Weather/WeatherSystem.cs @@ -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(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; } diff --git a/Content.Client/Wires/UI/WiresMenu.cs b/Content.Client/Wires/UI/WiresMenu.cs index eccc548297..77fc3accce 100644 --- a/Content.Client/Wires/UI/WiresMenu.cs +++ b/Content.Client/Wires/UI/WiresMenu.cs @@ -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}, diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs new file mode 100644 index 0000000000..d9cce764ab --- /dev/null +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs @@ -0,0 +1,108 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.Interaction; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.IntegrationTests.Tests.Buckle; + +public sealed partial class BuckleTest +{ + [Test] + public async Task BuckleInteractUnbuckleOther() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + var buckleSystem = entMan.System(); + + EntityUid user = default; + EntityUid victim = default; + EntityUid chair = default; + BuckleComponent buckle = null; + StrapComponent strap = null; + + await server.WaitAssertion(() => + { + user = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace); + victim = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace); + chair = entMan.SpawnEntity(StrapDummyId, MapCoordinates.Nullspace); + + Assert.That(entMan.TryGetComponent(victim, out buckle)); + Assert.That(entMan.TryGetComponent(chair, out strap)); + +#pragma warning disable RA0002 + buckle.Delay = TimeSpan.Zero; +#pragma warning restore RA0002 + + // Buckle victim to chair + Assert.That(buckleSystem.TryBuckle(victim, user, chair, buckle)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.EqualTo(chair), "Victim did not get buckled to the chair."); + Assert.That(buckle.Buckled, "Victim is not buckled."); + Assert.That(strap.BuckledEntities, Does.Contain(victim), "Chair does not have victim buckled to it."); + }); + + // InteractHand with chair to unbuckle victim + entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.Null); + Assert.That(buckle.Buckled, Is.False); + Assert.That(strap.BuckledEntities, Does.Not.Contain(victim)); + }); + }); + + await pair.CleanReturnAsync(); + } + + [Test] + public async Task BuckleInteractBuckleUnbuckleSelf() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + + EntityUid user = default; + EntityUid chair = default; + BuckleComponent buckle = null; + StrapComponent strap = null; + + await server.WaitAssertion(() => + { + user = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace); + chair = entMan.SpawnEntity(StrapDummyId, MapCoordinates.Nullspace); + + Assert.That(entMan.TryGetComponent(user, out buckle)); + Assert.That(entMan.TryGetComponent(chair, out strap)); + +#pragma warning disable RA0002 + buckle.Delay = TimeSpan.Zero; +#pragma warning restore RA0002 + + // Buckle user to chair + entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.EqualTo(chair), "Victim did not get buckled to the chair."); + Assert.That(buckle.Buckled, "Victim is not buckled."); + Assert.That(strap.BuckledEntities, Does.Contain(user), "Chair does not have victim buckled to it."); + }); + + // InteractHand with chair to unbuckle + entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair)); + Assert.Multiple(() => + { + Assert.That(buckle.BuckledTo, Is.Null); + Assert.That(buckle.Buckled, Is.False); + Assert.That(strap.BuckledEntities, Does.Not.Contain(user)); + }); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 156f42aac3..1b31fe38c2 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -15,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Buckle [TestFixture] [TestOf(typeof(BuckleComponent))] [TestOf(typeof(StrapComponent))] - public sealed class BuckleTest + public sealed partial class BuckleTest { private const string BuckleDummyId = "BuckleDummy"; private const string StrapDummyId = "StrapDummy"; diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs index ff4d34705a..9240e7b91b 100644 --- a/Content.Server/Administration/Commands/SetOutfitCommand.cs +++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs @@ -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(); 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(); + foreach (var job in jobs) + { + if (job.StartingGear != gear) + continue; + + var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID); + if (!prototypeManager.TryIndex(jobProtoId, out var jobProto)) + break; + + // Don't require a player, so this works on Urists + profile ??= entityManager.TryGetComponent(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(); + stationSpawning.EquipRoleLoadout(target, roleLoadout, jobProto); + } + return true; } } diff --git a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs index 59ef08402e..b1814c2741 100644 --- a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs +++ b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs @@ -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(OnAnomalyStabilityChanged); } + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + 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); + } + } + /// /// If powered, try to attach a nearby anomaly. /// @@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem if (args.Powered) return; - if (!TryComp(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 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 ent, AnomalyComponent anomaly) + private void DisconnectFromAnomaly(Entity ent, EntityUid other) { if (ent.Comp.ConnectedAnomaly == null) return; - if (ent.Comp.PulseOnDisconnect) - _anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly); + if (TryComp(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); diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 3e9760a056..b0de3de8f3 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -55,6 +55,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnStartCollide); + InitializeGenerator(); InitializeScanner(); InitializeVessel(); @@ -86,7 +87,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem private void OnShutdown(Entity 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 anomaly, ref StartCollideEvent args) diff --git a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs index 235e740cf3..3127f091e5 100644 --- a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs +++ b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs @@ -7,7 +7,7 @@ namespace Content.Server.Anomaly.Components; /// /// a device that allows you to translate anomaly activity into multitool signals. /// -[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))] +[RegisterComponent, AutoGenerateComponentPause, Access(typeof(AnomalySynchronizerSystem))] public sealed partial class AnomalySynchronizerComponent : Component { /// @@ -34,6 +34,15 @@ public sealed partial class AnomalySynchronizerComponent : Component [DataField] public float AttachRange = 0.4f; + /// + /// Periodicheski checks to see if the anomaly has moved to disconnect it. + /// + [DataField] + public TimeSpan CheckFrequency = TimeSpan.FromSeconds(1f); + + [DataField, AutoPausedField] + public TimeSpan NextCheckTime = TimeSpan.Zero; + [DataField] public ProtoId DecayingPort = "Decaying"; diff --git a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs new file mode 100644 index 0000000000..38c4c51d87 --- /dev/null +++ b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs @@ -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(OnStartCollideInjector); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnCompShutdown); + + SubscribeLocalEvent(OnAnomalyPulse); + SubscribeLocalEvent(OnAnomalyShutdown); + SubscribeLocalEvent(OnAnomalySupercritical); + SubscribeLocalEvent(OnSeverityChanged); + + SubscribeLocalEvent(OnMobStateChanged); + + SubscribeLocalEvent(OnActionPulse); + } + + private void OnActionPulse(Entity ent, ref ActionAnomalyPulseEvent args) + { + if (args.Handled) + return; + + _anomaly.DoAnomalyPulse(ent, ent.Comp); + + args.Handled = true; + } + + private void OnStartCollideInjector(Entity ent, ref StartCollideEvent args) + { + if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.OtherEntity)) + return; + if (TryComp(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 ent, ref MapInitEvent args) + { + AddAnomalyToBody(ent); + } + + private void AddAnomalyToBody(Entity 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 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 ent, ref AnomalySupercriticalEvent args) + { + if (!TryComp(ent, out var body)) + return; + + _body.GibBody(ent, true, body, splatModifier: 5f); + } + + private void OnSeverityChanged(Entity 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 ent, ref MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead) + return; + + _anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it + } + + private void OnAnomalyShutdown(Entity ent, ref AnomalyShutdownEvent args) + { + RemoveAnomalyFromBody(ent); + RemCompDeferred(ent); + } + + private void OnCompShutdown(Entity ent, ref ComponentShutdown args) + { + RemoveAnomalyFromBody(ent); + } + + private void RemoveAnomalyFromBody(Entity 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(ent); + } +} diff --git a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs index 1b2849f1d7..3e4d101f4f 100644 --- a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs @@ -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); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index db95223733..a03f27b561 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -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) { diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index 44bfa4cc10..13d8f73dc5 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -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 _firelockQuery; private HashSet _entSet = new(); + private string[] _burntDecals = []; + public override void Initialize() { base.Initialize(); @@ -66,7 +72,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem _firelockQuery = GetEntityQuery(); SubscribeLocalEvent(OnTileChanged); + SubscribeLocalEvent(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()) + 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().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray(); + } } diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs index 7a3e08883d..5608338f22 100644 --- a/Content.Server/Botany/SeedPrototype.cs +++ b/Content.Server/Botany/SeedPrototype.cs @@ -291,12 +291,13 @@ public partial class SeedData CanScream = CanScream, TurnIntoKudzu = TurnIntoKudzu, SplatPrototype = SplatPrototype, - Mutations = Mutations, + Mutations = new List(), // Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified. Unique = true, }; + newSeed.Mutations.AddRange(Mutations); return newSeed; } diff --git a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs index cd422328c3..7caec6150e 100644 --- a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs +++ b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs @@ -340,6 +340,9 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem if (args.Container.ID != InstalledContainerId && args.Container.ID != loader.CartridgeSlot.ID) return; + if (TryComp(args.Entity, out CartridgeComponent? cartridge)) + cartridge.LoaderUid = uid; + RaiseLocalEvent(args.Entity, new CartridgeAddedEvent(uid)); base.OnItemInserted(uid, loader, args); } @@ -360,6 +363,9 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem if (deactivate) RaiseLocalEvent(args.Entity, new CartridgeDeactivatedEvent(uid)); + if (TryComp(args.Entity, out CartridgeComponent? cartridge)) + cartridge.LoaderUid = null; + RaiseLocalEvent(args.Entity, new CartridgeRemovedEvent(uid)); base.OnItemRemoved(uid, loader, args); diff --git a/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs new file mode 100644 index 0000000000..08eef62379 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs @@ -0,0 +1,8 @@ +using Content.Shared.Security; + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent] +public sealed partial class WantedListCartridgeComponent : Component +{ +} diff --git a/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs b/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs index 28286d600e..6d284ab186 100644 --- a/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionRegenerationComponent.cs @@ -16,31 +16,31 @@ public sealed partial class SolutionRegenerationComponent : Component /// /// The name of the solution to add to. /// - [DataField("solution", required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField("solution", required: true)] public string SolutionName = string.Empty; /// /// The solution to add reagents to. /// - [DataField("solutionRef")] - public Entity? Solution = null; + [DataField] + public Entity? SolutionRef = null; /// /// The reagent(s) to be regenerated in the solution. /// - [DataField("generated", required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] public Solution Generated = default!; /// /// How long it takes to regenerate once. /// - [DataField("duration"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan Duration = TimeSpan.FromSeconds(1); /// /// The time when the next regeneration will occur. /// - [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextRegenTime = TimeSpan.FromSeconds(0); diff --git a/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs index 6a60062857..bccd594706 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionRegenerationSystem.cs @@ -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); } } } diff --git a/Content.Server/Construction/Completions/GivePrototype.cs b/Content.Server/Construction/Completions/GivePrototype.cs index f45bd5ba10..f05feb70c0 100644 --- a/Content.Server/Construction/Completions/GivePrototype.cs +++ b/Content.Server/Construction/Completions/GivePrototype.cs @@ -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))] - 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(Prototype)) { - if (string.IsNullOrEmpty(Prototype)) + var stackSystem = entityManager.EntitySysManager.GetEntitySystem(); + var stacks = stackSystem.SpawnMultiple(Prototype, Amount, userUid ?? uid); + + if (userUid is null || !entityManager.TryGetComponent(userUid, out HandsComponent? handsComp)) return; - var coordinates = entityManager.GetComponent(userUid ?? uid).Coordinates; - - if (EntityPrototypeHelpers.HasComponent(Prototype)) + foreach (var item in stacks) { - var stackEnt = entityManager.SpawnEntity(Prototype, coordinates); - var stack = entityManager.GetComponent(stackEnt); - entityManager.EntitySysManager.GetEntitySystem().SetCount(stackEnt, Amount, stack); - entityManager.EntitySysManager.GetEntitySystem().PickupOrDrop(userUid, stackEnt); + stackSystem.TryMergeToHands(item, userUid.Value, hands: handsComp); } - else + } + else + { + var handsSystem = entityManager.EntitySysManager.GetEntitySystem(); + var handsComp = userUid is not null ? entityManager.GetComponent(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().PickupOrDrop(userUid, item); - } + var item = entityManager.SpawnNextToOrDrop(Prototype, userUid ?? uid); + handsSystem.PickupOrDrop(userUid, item, handsComp: handsComp); } } } diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index c5f1d159f3..ca1d45e644 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -68,6 +68,13 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS } } + private void GetOfficer(EntityUid uid, out string officer) + { + var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, uid); + RaiseLocalEvent(tryGetIdentityShortInfoEvent); + officer = tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("criminal-records-console-unknown-officer"); + } + private void OnChangeStatus(Entity ent, ref CriminalRecordChangeStatus msg) { // prevent malf client violating wanted/reason nullability @@ -90,29 +97,22 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS return; } + var oldStatus = record.Status; + + var name = _records.RecordName(key.Value); + GetOfficer(mob.Value, out var officer); + // when arresting someone add it to history automatically // fallback exists if the player was not set to wanted beforehand if (msg.Status == SecurityStatus.Detained) { var oldReason = record.Reason ?? Loc.GetString("criminal-records-console-unspecified-reason"); var history = Loc.GetString("criminal-records-console-auto-history", ("reason", oldReason)); - _criminalRecords.TryAddHistory(key.Value, history); + _criminalRecords.TryAddHistory(key.Value, history, officer); } - var oldStatus = record.Status; - // will probably never fail given the checks above - _criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason); - - var name = _records.RecordName(key.Value); - var officer = Loc.GetString("criminal-records-console-unknown-officer"); - - var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value); - RaiseLocalEvent(tryGetIdentityShortInfoEvent); - if (tryGetIdentityShortInfoEvent.Title != null) - { - officer = tryGetIdentityShortInfoEvent.Title; - } + _criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer); (string, object)[] args; if (reason != null) @@ -152,14 +152,16 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS private void OnAddHistory(Entity ent, ref CriminalRecordAddHistory msg) { - if (!CheckSelected(ent, msg.Actor, out _, out var key)) + if (!CheckSelected(ent, msg.Actor, out var mob, out var key)) return; var line = msg.Line.Trim(); if (line.Length < 1 || line.Length > ent.Comp.MaxStringLength) return; - if (!_criminalRecords.TryAddHistory(key.Value, line)) + GetOfficer(mob.Value, out var officer); + + if (!_criminalRecords.TryAddHistory(key.Value, line, officer)) return; // no radio message since its not crucial to officers patrolling diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs index a65fb0be9e..7c65ce8c24 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs @@ -1,10 +1,15 @@ -using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.CartridgeLoader; +using Content.Server.CartridgeLoader.Cartridges; using Content.Server.StationRecords.Systems; using Content.Shared.CriminalRecords; using Content.Shared.CriminalRecords.Systems; using Content.Shared.Security; using Content.Shared.StationRecords; using Content.Server.GameTicking; +using Content.Server.Station.Systems; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; namespace Content.Server.CriminalRecords.Systems; @@ -20,12 +25,18 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem { [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly StationRecordsSystem _records = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly CartridgeLoaderSystem _cartridge = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnGeneralRecordCreated); + SubscribeLocalEvent(OnRecordChanged); + SubscribeLocalEvent(OnCartridgeUiReady); + SubscribeLocalEvent(OnHistoryAdded); + SubscribeLocalEvent(OnHistoryRemoved); } private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev) @@ -39,14 +50,14 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem /// Reason should only be passed if status is Wanted, nullability isn't checked. /// /// True if the status is changed, false if not - public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason) + public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason, string? initiatorName = null) { // don't do anything if its the same status if (!_records.TryGetRecord(key, out var record) || status == record.Status) return false; - OverwriteStatus(key, record, status, reason); + OverwriteStatus(key, record, status, reason, initiatorName); return true; } @@ -54,16 +65,24 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem /// /// Sets the status without checking previous status or reason nullability. /// - public void OverwriteStatus(StationRecordKey key, CriminalRecord record, SecurityStatus status, string? reason) + public void OverwriteStatus(StationRecordKey key, CriminalRecord record, SecurityStatus status, string? reason, string? initiatorName = null) { record.Status = status; record.Reason = reason; + record.InitiatorName = initiatorName; var name = _records.RecordName(key); if (name != string.Empty) UpdateCriminalIdentity(name, status); _records.Synchronize(key); + + var args = new CriminalRecordChangedEvent(record); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } } /// @@ -76,15 +95,23 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem return false; record.History.Add(entry); + + var args = new CriminalHistoryAddedEvent(entry); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } + return true; } /// /// Creates and tries to add a history entry using the current time. /// - public bool TryAddHistory(StationRecordKey key, string line) + public bool TryAddHistory(StationRecordKey key, string line, string? initiatorName = null) { - var entry = new CrimeHistory(_ticker.RoundDuration(), line); + var entry = new CrimeHistory(_ticker.RoundDuration(), line, initiatorName); return TryAddHistory(key, entry); } @@ -100,7 +127,58 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem if (index >= record.History.Count) return false; + var history = record.History[(int)index]; record.History.RemoveAt((int) index); + + var args = new CriminalHistoryRemovedEvent(history); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } + return true; } + + private void OnRecordChanged(Entity ent, ref CriminalRecordChangedEvent args) => + StateChanged(ent); + + private void OnHistoryAdded(Entity ent, ref CriminalHistoryAddedEvent args) => + StateChanged(ent); + + private void OnHistoryRemoved(Entity ent, ref CriminalHistoryRemovedEvent args) => + StateChanged(ent); + + private void StateChanged(Entity ent) + { + if (Comp(ent).LoaderUid is not { } loaderUid) + return; + + UpdateReaderUi(ent, loaderUid); + } + + private void OnCartridgeUiReady(Entity ent, ref CartridgeUiReadyEvent args) + { + UpdateReaderUi(ent, args.Loader); + } + + private void UpdateReaderUi(Entity ent, EntityUid loaderUid) + { + if (_station.GetOwningStation(ent) is not { } station) + return; + + var records = _records.GetRecordsOfType(station) + .Where(cr => cr.Item2.Status is not SecurityStatus.None || cr.Item2.History.Count > 0) + .Select(cr => + { + var (i, r) = cr; + var key = new StationRecordKey(i, station); + // Hopefully it will work smoothly..... + _records.TryGetRecord(key, out GeneralStationRecord? generalRecord); + return new WantedRecord(generalRecord!, r.Status, r.Reason, r.InitiatorName, r.History); + }); + var state = new WantedListUiState(records.ToList()); + + _cartridge.UpdateCartridgeUiState(loaderUid, state); + } } diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs index 5166aaccab..08c7c8f068 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs @@ -1,4 +1,4 @@ -using Content.Server.Explosion.Components; +using Content.Shared.Explosion.Components; using JetBrains.Annotations; namespace Content.Server.Destructible.Thresholds.Behaviors diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index 8391e8faad..407f877515 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -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()) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs index 4b59c8f1c4..6fa553bc8b 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Airtight.cs @@ -6,9 +6,10 @@ using Content.Shared.Explosion; using Content.Shared.Explosion.EntitySystems; using Content.Shared.FixedPoint; using Robust.Shared.Map.Components; + namespace Content.Server.Explosion.EntitySystems; -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { [Dependency] private readonly DestructibleSystem _destructibleSystem = default!; diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs index ce98f89de7..5af06ef936 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs @@ -1,8 +1,8 @@ using Content.Shared.CCVar; -using Content.Shared.Explosion.EntitySystems; + namespace Content.Server.Explosion.EntitySystems; -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { public int MaxIterations { get; private set; } public int MaxArea { get; private set; } diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs index 75bb606441..29477c16b2 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs @@ -12,7 +12,7 @@ namespace Content.Server.Explosion.EntitySystems; // A good portion of it is focused around keeping track of what tile-indices on a grid correspond to tiles that border // space. AFAIK no other system currently needs to track these "edge-tiles". If they do, this should probably be a // property of the grid itself? -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { /// /// Set of tiles of each grid that are directly adjacent to space, along with the directions that face space. diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 6d0cbcf279..97d52e436a 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -22,9 +22,10 @@ using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; + namespace Content.Server.Explosion.EntitySystems; -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { [Dependency] private readonly FlammableSystem _flammableSystem = default!; @@ -218,7 +219,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem // get the entities on a tile. Note that we cannot process them directly, or we get // enumerator-changed-while-enumerating errors. List<(EntityUid, TransformComponent)> list = new(); - var state = (list, processed, _transformQuery); + var state = (list, processed, EntityManager.TransformQuery); // get entities: lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); @@ -317,7 +318,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem var gridBox = Box2.FromDimensions(tile * DefaultTileSize, new Vector2(DefaultTileSize, DefaultTileSize)); var worldBox = spaceMatrix.TransformBox(gridBox); var list = new List<(EntityUid, TransformComponent)>(); - var state = (list, processed, invSpaceMatrix, lookup.Owner, _transformQuery, gridBox, _transformSystem); + var state = (list, processed, invSpaceMatrix, lookup.Owner, EntityManager.TransformQuery, gridBox, _transformSystem); // get entities: lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs index 8c3229e06e..7b73490d94 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs @@ -7,13 +7,13 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; -using Content.Shared.Explosion.EntitySystems; + namespace Content.Server.Explosion.EntitySystems; // This partial part of the explosion system has all of the functions used to create the actual explosion map. // I.e, to get the sets of tiles & intensity values that describe an explosion. -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { /// /// This is the main explosion generating function. diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs index 219dba4bde..57323e4de7 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs @@ -5,10 +5,11 @@ using Content.Shared.Explosion.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Map; + namespace Content.Server.Explosion.EntitySystems; // This part of the system handled send visual / overlay data to clients. -public sealed partial class ExplosionSystem : SharedExplosionSystem +public sealed partial class ExplosionSystem { public void InitVisuals() { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index cd0ca1c22e..818953ed4b 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -12,6 +12,8 @@ using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Explosion; +using Content.Shared.Explosion.Components; +using Content.Shared.Explosion.EntitySystems; using Content.Shared.GameTicking; using Content.Shared.Inventory; using Content.Shared.Projectiles; @@ -53,7 +55,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedMapSystem _map = default!; - private EntityQuery _transformQuery; private EntityQuery _flammableQuery; private EntityQuery _physicsQuery; private EntityQuery _projectileQuery; @@ -103,7 +104,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem InitAirtightMap(); InitVisuals(); - _transformQuery = GetEntityQuery(); _flammableQuery = GetEntityQuery(); _physicsQuery = GetEntityQuery(); _projectileQuery = GetEntityQuery(); @@ -141,15 +141,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem args.DamageCoefficient *= modifier; } - /// - /// Given an entity with an explosive component, spawn the appropriate explosion. - /// - /// - /// Also accepts radius or intensity arguments. This is useful for explosives where the intensity is not - /// specified in the yaml / by the component, but determined dynamically (e.g., by the quantity of a - /// solution in a reaction). - /// - public void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) + /// + public override void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) { // log missing: false, because some entities (e.g. liquid tanks) attempt to trigger explosions when damaged, // but may not actually be explosive. diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 853b4fbc29..57847551aa 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -202,6 +202,7 @@ namespace Content.Server.Explosion.EntitySystems args.Handled = true; } + private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args) { if (!TryComp(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) diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index ccb58e94f8..fb449a372c 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -152,7 +152,7 @@ namespace Content.Server.Flash } } - public void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) + public override void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { var transform = Transform(source); var mapPosition = _transform.GetMapCoordinates(transform); diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 9f43f76018..1ac02ac882 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -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 ent, ref ComponentShutdown args) diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 71536ddf8e..12c5a30a4b 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -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)); } diff --git a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs index aa28a8063c..08120f5c29 100644 --- a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs +++ b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs @@ -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 diff --git a/Content.Server/Objectives/Systems/StealConditionSystem.cs b/Content.Server/Objectives/Systems/StealConditionSystem.cs index be34a80fe3..e2d81e011c 100644 --- a/Content.Server/Objectives/Systems/StealConditionSystem.cs +++ b/Content.Server/Objectives/Systems/StealConditionSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Objectives.Components; using Content.Server.Objectives.Components.Targets; +using Content.Shared.CartridgeLoader; using Content.Shared.Mind; using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Systems; @@ -172,6 +173,11 @@ public sealed class StealConditionSystem : EntitySystem if (target.StealGroup != condition.StealGroup) return 0; + // check if cartridge is installed + if (TryComp(entity, out var cartridge) && + cartridge.InstallationStatus is not InstallationStatus.Cartridge) + return 0; + // check if needed target alive if (condition.CheckAlive) { diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs index 9fb7d5ff1f..027f257040 100644 --- a/Content.Server/Power/Generation/Teg/TegSystem.cs +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -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(uid); var powerReceiver = Comp(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; diff --git a/Content.Server/Respawn/SpecialRespawnSystem.cs b/Content.Server/Respawn/SpecialRespawnSystem.cs index 8c86449008..6d398db259 100644 --- a/Content.Server/Respawn/SpecialRespawnSystem.cs +++ b/Content.Server/Respawn/SpecialRespawnSystem.cs @@ -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; diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 523b5e86a8..2c10e6d929 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -126,7 +126,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; @@ -144,7 +144,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; @@ -184,7 +184,7 @@ namespace Content.Server.RoundEnd _chatSystem.DispatchGlobalAnnouncement(Loc.GetString(text, ("time", time), ("units", Loc.GetString(units))), - name, + Loc.GetString(name), false, null, colorOverride: Color.Gold); // Sunrise-TTS diff --git a/Content.Server/Salvage/SalvageSystem.Runner.cs b/Content.Server/Salvage/SalvageSystem.Runner.cs index 23a575413e..921d966709 100644 --- a/Content.Server/Salvage/SalvageSystem.Runner.cs +++ b/Content.Server/Salvage/SalvageSystem.Runner.cs @@ -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); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 89d07c403d..f809321550 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -398,7 +398,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. diff --git a/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs index 05262f2999..6d97c8ccb3 100644 --- a/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs @@ -37,6 +37,7 @@ public sealed class ContainmentFieldGeneratorSystem : EntitySystem SubscribeLocalEvent(OnUnanchorAttempt); SubscribeLocalEvent(OnComponentRemoved); SubscribeLocalEvent(PreventBreach); + SubscribeLocalEvent(OnMapInit); } public override void Update(float frameTime) @@ -61,6 +62,12 @@ public sealed class ContainmentFieldGeneratorSystem : EntitySystem #region Events + private void OnMapInit(Entity generator, ref MapInitEvent args) + { + if (generator.Comp.Enabled) + ChangeFieldVisualizer(generator); + } + /// /// A generator receives power from a source colliding with it. /// diff --git a/Content.Shared/Anomaly/Components/AnomalyComponent.cs b/Content.Shared/Anomaly/Components/AnomalyComponent.cs index 724dfd38d2..e6228b5fb0 100644 --- a/Content.Shared/Anomaly/Components/AnomalyComponent.cs +++ b/Content.Shared/Anomaly/Components/AnomalyComponent.cs @@ -1,6 +1,6 @@ using System.Numerics; +using Content.Shared.Anomaly.Effects; using Content.Shared.Anomaly.Prototypes; -using Content.Shared.Damage; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -16,7 +16,7 @@ namespace Content.Shared.Anomaly.Components; /// Anomalies and their related components were designed here: https://hackmd.io/@ss14-design/r1sQbkJOs /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] -[Access(typeof(SharedAnomalySystem))] +[Access(typeof(SharedAnomalySystem), typeof(SharedInnerBodyAnomalySystem))] public sealed partial class AnomalyComponent : Component { /// @@ -184,21 +184,21 @@ public sealed partial class AnomalyComponent : Component /// /// The minimum amount of research points generated per second /// - [DataField("minPointsPerSecond")] + [DataField] public int MinPointsPerSecond = 10; /// /// The maximum amount of research points generated per second /// This doesn't include the point bonus for being unstable. /// - [DataField("maxPointsPerSecond")] + [DataField] public int MaxPointsPerSecond = 70; /// /// The multiplier applied to the point value for the /// anomaly being above the /// - [DataField("growingPointMultiplier")] + [DataField] public float GrowingPointMultiplier = 1.5f; #endregion @@ -252,10 +252,13 @@ public sealed partial class AnomalyComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField("offset")] - public Vector2 FloatingOffset = new(0, 0.15f); + public Vector2 FloatingOffset = new(0, 0); public readonly string AnimationKey = "anomalyfloat"; #endregion + + [DataField] + public bool DeleteEntity = true; } /// diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs new file mode 100644 index 0000000000..e88cedb18e --- /dev/null +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs @@ -0,0 +1,72 @@ +using Content.Shared.Anomaly.Effects; +using Content.Shared.Body.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Anomaly.Components; + +/// +/// An anomaly within the body of a living being. Controls the ability to return to the standard state. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedInnerBodyAnomalySystem))] +public sealed partial class InnerBodyAnomalyComponent : Component +{ + [DataField] + public bool Injected; + + /// + /// A prototype of an entity whose components will be added to the anomaly host **AND** then removed at the right time + /// + [DataField(required: true)] + public EntProtoId? InjectionProto; + + /// + /// Duration of stun from the effect of the anomaly + /// + [DataField] + public float StunDuration = 4f; + + /// + /// A message sent in chat to a player who has become infected by an anomaly + /// + [DataField] + public LocId? StartMessage = null; + + /// + /// A message sent in chat to a player who has cleared an anomaly + /// + [DataField] + public LocId? EndMessage = "inner-anomaly-end-message"; + + /// + /// Sound, playing on becoming anomaly + /// + [DataField] + public SoundSpecifier? StartSound = new SoundPathSpecifier("/Audio/Effects/inneranomaly.ogg"); + + /// + /// Used to display messages to the player about their level of disease progression + /// + [DataField] + public float LastSeverityInformed = 0f; + + /// + /// The fallback sprite to be added on the original entity. Allows you to visually identify the feature and type of anomaly to other players + /// + [DataField, AutoNetworkedField] + public SpriteSpecifier? FallbackSprite = null; + + /// + /// Ability to use unique sprites for different body types + /// + [DataField, AutoNetworkedField] + public Dictionary, SpriteSpecifier> SpeciesSprites = new(); + + /// + /// The key of the entity layer into which the sprite will be inserted + /// + [DataField] + public string LayerMap = "inner_anomaly_layer"; +} diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs new file mode 100644 index 0000000000..e4c398c9cc --- /dev/null +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyInjectorComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Anomaly.Effects; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Anomaly.Components; + +/// +/// On contact with an entity, if it meets the conditions, it will transfer the specified components to it +/// +[RegisterComponent, Access(typeof(SharedInnerBodyAnomalySystem))] +public sealed partial class InnerBodyAnomalyInjectorComponent : Component +{ + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// components that will be automatically removed after “curing” + /// + [DataField(required: true)] + public ComponentRegistry InjectionComponents = default!; +} diff --git a/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs b/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs new file mode 100644 index 0000000000..a1ec7cd397 --- /dev/null +++ b/Content.Shared/Anomaly/Effects/SharedInnerBodyAnomalySystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Anomaly.Effects; + +public abstract class SharedInnerBodyAnomalySystem : EntitySystem +{ +} diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index c3d6591b72..07beb1444d 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -1,13 +1,10 @@ using Content.Shared.Administration.Logs; using Content.Shared.Anomaly.Components; using Content.Shared.Anomaly.Prototypes; -using Content.Shared.Damage; using Content.Shared.Database; -using Content.Shared.Interaction; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Weapons.Melee.Components; -using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -21,6 +18,7 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using System.Linq; using System.Numerics; +using Content.Shared.Actions; namespace Content.Shared.Anomaly; @@ -36,6 +34,7 @@ public abstract class SharedAnomalySystem : EntitySystem [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { @@ -145,7 +144,7 @@ public abstract class SharedAnomalySystem : EntitySystem if (!Timing.IsFirstTimePredicted) return; - Audio.PlayPvs(component.SupercriticalSound, uid); + Audio.PlayPvs(component.SupercriticalSound, Transform(uid).Coordinates); if (_net.IsServer) Log.Info($"Raising supercritical event. Entity: {ToPrettyString(uid)}"); @@ -169,7 +168,8 @@ public abstract class SharedAnomalySystem : EntitySystem /// The anomaly being shut down /// /// Whether or not the anomaly ended via supercritical event - public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false) + /// Create anomaly cores based on the result of completing an anomaly? + public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true) { // Logging before resolve, in case the anomaly has deleted itself. if (_net.IsServer) @@ -186,9 +186,16 @@ public abstract class SharedAnomalySystem : EntitySystem if (Terminating(uid) || _net.IsClient) return; - Spawn(supercritical ? component.CorePrototype : component.CoreInertPrototype, Transform(uid).Coordinates); + if (spawnCore) + { + var core = Spawn(supercritical ? component.CorePrototype : component.CoreInertPrototype, Transform(uid).Coordinates); + _transform.PlaceNextTo(core, uid); + } - QueueDel(uid); + if (component.DeleteEntity) + QueueDel(uid); + else + RemCompDeferred(uid); } /// @@ -458,3 +465,5 @@ public partial record struct AnomalySpawnSettings() /// public bool SpawnOnSeverityChanged { get; set; } = false; } + +public sealed partial class ActionAnomalyPulseEvent : InstantActionEvent { } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs b/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs index 7677e800fe..1a15e52a3c 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs @@ -1,5 +1,5 @@ +using System.Linq; using Content.Shared.Buckle.Components; -using Content.Shared.Cuffs.Components; using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.IdentityManagement; @@ -84,15 +84,29 @@ public abstract partial class SharedBuckleSystem if (!TryComp(args.User, out BuckleComponent? buckle)) return; - if (buckle.BuckledTo == null && component.BuckleOnInteractHand) + // Buckle self + if (buckle.BuckledTo == null && component.BuckleOnInteractHand && StrapHasSpace(uid, buckle, component)) + { TryBuckle(args.User, args.User, uid, buckle, popup: true); - else if (buckle.BuckledTo == uid) - TryUnbuckle(args.User, args.User, buckle, popup: true); - else + args.Handled = true; return; + } + + // Unbuckle self + if (buckle.BuckledTo == uid && TryUnbuckle(args.User, args.User, buckle, popup: true)) + { + args.Handled = true; + return; + } + + // Unbuckle others + if (component.BuckledEntities.TryFirstOrNull(out var buckled) && TryUnbuckle(buckled.Value, args.User)) + { + args.Handled = true; + return; + } // TODO BUCKLE add out bool for whether a pop-up was generated or not. - args.Handled = true; } private void OnBuckleInteractHand(Entity ent, ref InteractHandEvent args) diff --git a/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs new file mode 100644 index 0000000000..9d55e0c163 --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs @@ -0,0 +1,11 @@ +using Content.Shared.CriminalRecords; +using Content.Shared.CriminalRecords.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class WantedListUiState(List records) : BoundUserInterfaceState +{ + public List Records = records; +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index f41fa2b22d..f25273f403 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -359,39 +359,76 @@ namespace Content.Shared.Containers.ItemSlots /// Useful for predicted interactions /// /// False if failed to insert item - public bool TryInsertEmpty(Entity ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false) + public bool TryInsertEmpty(Entity ent, + EntityUid item, + EntityUid? user, + bool excludeUserAudio = false) { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + TryComp(user, out HandsComponent? handsComp); + + if (!TryGetAvailableSlot(ent, + item, + user == null ? null : (user.Value, handsComp), + out var itemSlot, + emptyOnly: true)) + return false; + + if (user != null && !_handsSystem.TryDrop(user.Value, item, handsComp: handsComp)) + return false; + + Insert(ent, itemSlot, item, user, excludeUserAudio: excludeUserAudio); + return true; + } + + /// + /// Tries to get any slot that the can be inserted into. + /// + /// Entity that is being inserted into. + /// Entity being inserted into . + /// Entity inserting into . + /// The ItemSlot on to insert into. + /// True only returns slots that are empty. + /// False returns any slot that is able to receive . + /// True when a slot is found. Otherwise, false. + public bool TryGetAvailableSlot(Entity ent, + EntityUid item, + Entity? userEnt, + [NotNullWhen(true)] out ItemSlot? itemSlot, + bool emptyOnly = false) + { + itemSlot = null; + + if (userEnt is { } user + && Resolve(user, ref user.Comp) + && _handsSystem.IsHolding(user, item)) + { + if (!_handsSystem.CanDrop(user, item, user.Comp)) + return false; + } + if (!Resolve(ent, ref ent.Comp, false)) return false; var slots = new List(); foreach (var slot in ent.Comp.Slots.Values) { - if (slot.ContainerSlot?.ContainedEntity != null) + if (emptyOnly && slot.ContainerSlot?.ContainedEntity != null) continue; - if (CanInsert(ent, item, user, slot)) + if (CanInsert(ent, item, userEnt, slot)) slots.Add(slot); } if (slots.Count == 0) return false; - if (user != null && _handsSystem.IsHolding(user.Value, item)) - { - if (!_handsSystem.TryDrop(user.Value, item)) - return false; - } - slots.Sort(SortEmpty); - foreach (var slot in slots) - { - if (TryInsert(ent, slot, item, user, excludeUserAudio: excludeUserAudio)) - return true; - } - - return false; + itemSlot = slots[0]; + return true; } private static int SortEmpty(ItemSlot a, ItemSlot b) diff --git a/Content.Shared/CriminalRecords/CriminalRecord.cs b/Content.Shared/CriminalRecords/CriminalRecord.cs index 0fe23d4395..5a023a9188 100644 --- a/Content.Shared/CriminalRecords/CriminalRecord.cs +++ b/Content.Shared/CriminalRecords/CriminalRecord.cs @@ -23,6 +23,12 @@ public sealed record CriminalRecord [DataField] public string? Reason; + /// + /// The name of the person who changed the status. + /// + [DataField] + public string? InitiatorName; + /// /// Criminal history of the person. /// This should have charges and time served added after someone is detained. @@ -35,4 +41,4 @@ public sealed record CriminalRecord /// A line of criminal activity and the time it was added at. /// [Serializable, NetSerializable] -public record struct CrimeHistory(TimeSpan AddTime, string Crime); +public record struct CrimeHistory(TimeSpan AddTime, string Crime, string? InitiatorName); diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs index 96b33ab91b..d665d32f1e 100644 --- a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs +++ b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs @@ -2,6 +2,8 @@ using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement.Components; using Content.Shared.Security; using Content.Shared.Security.Components; +using Content.Shared.StationRecords; +using Robust.Shared.Serialization; namespace Content.Shared.CriminalRecords.Systems; @@ -50,3 +52,22 @@ public abstract class SharedCriminalRecordsSystem : EntitySystem Dirty(characterUid, record); } } + +[Serializable, NetSerializable] +public struct WantedRecord(GeneralStationRecord targetInfo, SecurityStatus status, string? reason, string? initiator, List history) +{ + public GeneralStationRecord TargetInfo = targetInfo; + public SecurityStatus Status = status; + public string? Reason = reason; + public string? Initiator = initiator; + public List History = history; +}; + +[ByRefEvent] +public record struct CriminalRecordChangedEvent(CriminalRecord Record); + +[ByRefEvent] +public record struct CriminalHistoryAddedEvent(CrimeHistory History); + +[ByRefEvent] +public record struct CriminalHistoryRemovedEvent(CrimeHistory History); diff --git a/Content.Server/Explosion/Components/ExplosiveComponent.cs b/Content.Shared/Explosion/Components/ExplosiveComponent.cs similarity index 76% rename from Content.Server/Explosion/Components/ExplosiveComponent.cs rename to Content.Shared/Explosion/Components/ExplosiveComponent.cs index 2b27a89d9d..bab7f5a7d6 100644 --- a/Content.Server/Explosion/Components/ExplosiveComponent.cs +++ b/Content.Shared/Explosion/Components/ExplosiveComponent.cs @@ -1,8 +1,7 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Explosion; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Content.Shared.Explosion.EntitySystems; +using Robust.Shared.Prototypes; -namespace Content.Server.Explosion.Components; +namespace Content.Shared.Explosion.Components; /// /// Specifies an explosion that can be spawned by this entity. The explosion itself is spawned via -[RegisterComponent] +[RegisterComponent, Access(typeof(SharedExplosionSystem))] public sealed partial class ExplosiveComponent : Component { - /// /// The explosion prototype. This determines the damage types, the tile-break chance, and some visual /// information (e.g., the light that the explosion gives off). /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("explosionType", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ExplosionType = default!; + [DataField(required: true)] + public ProtoId ExplosionType = default!; /// /// The maximum intensity the explosion can have on a single tile. This limits the maximum damage and tile /// break chance the explosion can achieve at any given location. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxIntensity")] + [DataField] public float MaxIntensity = 4; /// /// How quickly the intensity drops off as you move away from the epicenter. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("intensitySlope")] + [DataField] public float IntensitySlope = 1; /// @@ -47,38 +42,34 @@ public sealed partial class ExplosiveComponent : Component /// This number can be overridden by passing optional argument to . /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("totalIntensity")] + [DataField] public float TotalIntensity = 10; /// /// Factor used to scale the explosion intensity when calculating tile break chances. Allows for stronger /// explosives that don't space tiles, without having to create a new explosion-type prototype. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("tileBreakScale")] + [DataField] public float TileBreakScale = 1f; /// /// Maximum number of times that an explosive can break a tile. Currently, for normal space stations breaking a /// tile twice will generally result in a vacuum. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxTileBreak")] + [DataField] public int MaxTileBreak = int.MaxValue; /// /// Whether this explosive should be able to create a vacuum by breaking tiles. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canCreateVacuum")] + [DataField] public bool CanCreateVacuum = true; /// /// An override for whether or not the entity should be deleted after it explodes. /// If null, the system calling the explode method handles it. /// - [DataField("deleteAfterExplosion")] + [DataField] public bool? DeleteAfterExplosion; /// diff --git a/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs index 1d926dd0b6..f298255807 100644 --- a/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs +++ b/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs @@ -1,25 +1,40 @@ -using Content.Shared.Explosion.Components; using Content.Shared.Armor; +using Content.Shared.Explosion.Components; namespace Content.Shared.Explosion.EntitySystems; +/// +/// Lets code in shared trigger explosions and handles explosion resistance examining. +/// All processing is still done clientside. +/// public abstract class SharedExplosionSystem : EntitySystem { - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnArmorExamine); } - private void OnArmorExamine(EntityUid uid, ExplosionResistanceComponent component, ref ArmorExamineEvent args) + private void OnArmorExamine(Entity ent, ref ArmorExamineEvent args) { - var value = MathF.Round((1f - component.DamageCoefficient) * 100, 1); + var value = MathF.Round((1f - ent.Comp.DamageCoefficient) * 100, 1); if (value == 0) return; args.Msg.PushNewline(); - args.Msg.AddMarkupOrThrow(Loc.GetString(component.Examine, ("value", value))); + args.Msg.AddMarkupOrThrow(Loc.GetString(ent.Comp.Examine, ("value", value))); + } + + /// + /// Given an entity with an explosive component, spawn the appropriate explosion. + /// + /// + /// Also accepts radius or intensity arguments. This is useful for explosives where the intensity is not + /// specified in the yaml / by the component, but determined dynamically (e.g., by the quantity of a + /// solution in a reaction). + /// + public virtual void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) + { } } diff --git a/Content.Shared/Flash/SharedFlashSystem.cs b/Content.Shared/Flash/SharedFlashSystem.cs index f83f02a310..b778809887 100644 --- a/Content.Shared/Flash/SharedFlashSystem.cs +++ b/Content.Shared/Flash/SharedFlashSystem.cs @@ -1,10 +1,15 @@ +using Content.Shared.Flash.Components; using Content.Shared.StatusEffect; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; -namespace Content.Shared.Flash +namespace Content.Shared.Flash; + +public abstract class SharedFlashSystem : EntitySystem { - public abstract class SharedFlashSystem : EntitySystem + public ProtoId FlashedKey = "Flashed"; + + public virtual void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { - [ValidatePrototypeId] - public const string FlashedKey = "Flashed"; } } diff --git a/Content.Shared/Fluids/Components/DrainComponent.cs b/Content.Shared/Fluids/Components/DrainComponent.cs index 4fb4fe9438..50cb5f5195 100644 --- a/Content.Shared/Fluids/Components/DrainComponent.cs +++ b/Content.Shared/Fluids/Components/DrainComponent.cs @@ -23,14 +23,14 @@ public sealed partial class DrainComponent : Component [DataField] public Entity? Solution = null; - [DataField("accumulator")] + [DataField] public float Accumulator = 0f; /// /// Does this drain automatically absorb surrouding puddles? Or is it a drain designed to empty - /// solutions in it manually? + /// solutions in it manually? /// - [DataField("autoDrain"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public bool AutoDrain = true; /// @@ -38,47 +38,47 @@ public sealed partial class DrainComponent : Component /// Divided by puddles, so if there are 5 puddles this will take 1/5 from each puddle. /// This will stay fixed to 1 second no matter what DrainFrequency is. /// - [DataField("unitsPerSecond")] + [DataField] public float UnitsPerSecond = 6f; /// /// How many units are ejected from the buffer per second. /// - [DataField("unitsDestroyedPerSecond")] + [DataField] public float UnitsDestroyedPerSecond = 3f; /// /// How many (unobstructed) tiles away the drain will /// drain puddles from. /// - [DataField("range"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Range = 2f; /// /// How often in seconds the drain checks for puddles around it. /// If the EntityQuery seems a bit unperformant this can be increased. /// - [DataField("drainFrequency")] + [DataField] public float DrainFrequency = 1f; /// /// How much time it takes to unclog it with a plunger /// - [DataField("unclogDuration"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float UnclogDuration = 1f; /// /// What's the probability of uncloging on each try /// - [DataField("unclogProbability"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float UnclogProbability = 0.75f; - [DataField("manualDrainSound"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public SoundSpecifier ManualDrainSound = new SoundPathSpecifier("/Audio/Effects/Fluids/slosh.ogg"); - [DataField("plungerSound"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public SoundSpecifier PlungerSound = new SoundPathSpecifier("/Audio/Items/Janitor/plunger.ogg"); - [DataField("unclogSound"), ViewVariables(VVAccess.ReadOnly)] + [DataField] public SoundSpecifier UnclogSound = new SoundPathSpecifier("/Audio/Effects/Fluids/glug.ogg"); } diff --git a/Content.Shared/Interaction/Events/InteractionFailureEvent.cs b/Content.Shared/Interaction/Events/InteractionFailureEvent.cs new file mode 100644 index 0000000000..a820048104 --- /dev/null +++ b/Content.Shared/Interaction/Events/InteractionFailureEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Interaction.Events; + +/// +/// Raised on the target when failing to pet/hug something. +/// +[ByRefEvent] +public readonly record struct InteractionFailureEvent(EntityUid User); diff --git a/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs b/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs new file mode 100644 index 0000000000..da4f9e43d7 --- /dev/null +++ b/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Interaction.Events; + +/// +/// Raised on the target when successfully petting/hugging something. +/// +[ByRefEvent] +public readonly record struct InteractionSuccessEvent(EntityUid User); diff --git a/Content.Shared/Interaction/InteractionPopupSystem.cs b/Content.Shared/Interaction/InteractionPopupSystem.cs index 2a742d4211..20c079dfd8 100644 --- a/Content.Shared/Interaction/InteractionPopupSystem.cs +++ b/Content.Shared/Interaction/InteractionPopupSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Bed.Sleep; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Components; +using Content.Shared.Interaction.Events; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; @@ -100,6 +101,9 @@ public sealed class InteractionPopupSystem : EntitySystem if (component.InteractSuccessSpawn != null) Spawn(component.InteractSuccessSpawn, _transform.GetMapCoordinates(uid)); + + var ev = new InteractionSuccessEvent(user); + RaiseLocalEvent(target, ref ev); } else { @@ -111,6 +115,9 @@ public sealed class InteractionPopupSystem : EntitySystem if (component.InteractFailureSpawn != null) Spawn(component.InteractFailureSpawn, _transform.GetMapCoordinates(uid)); + + var ev = new InteractionFailureEvent(user); + RaiseLocalEvent(target, ref ev); } if (!string.IsNullOrEmpty(component.MessagePerceivedByOthers)) diff --git a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs index a9a9390a6e..c332064ea3 100644 --- a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs +++ b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; +using Content.Shared.Storage; using Content.Shared.Storage.Components; namespace Content.Shared.Placeable; @@ -8,12 +9,16 @@ namespace Content.Shared.Placeable; public sealed class PlaceableSurfaceSystem : EntitySystem { [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAfterInteractUsing); + SubscribeLocalEvent(OnStorageInteractUsingAttempt); + SubscribeLocalEvent(OnStorageAfterOpen); + SubscribeLocalEvent(OnStorageAfterClose); } public void SetPlaceable(EntityUid uid, bool isPlaceable, PlaceableSurfaceComponent? surface = null) @@ -21,6 +26,9 @@ public sealed class PlaceableSurfaceSystem : EntitySystem if (!Resolve(uid, ref surface, false)) return; + if (surface.IsPlaceable == isPlaceable) + return; + surface.IsPlaceable = isPlaceable; Dirty(uid, surface); } @@ -59,11 +67,24 @@ public sealed class PlaceableSurfaceSystem : EntitySystem if (!_handsSystem.TryDrop(args.User, args.Used)) return; - if (surface.PlaceCentered) - Transform(args.Used).LocalPosition = Transform(uid).LocalPosition + surface.PositionOffset; - else - Transform(args.Used).Coordinates = args.ClickLocation; + _transformSystem.SetCoordinates(args.Used, + surface.PlaceCentered ? Transform(uid).Coordinates.Offset(surface.PositionOffset) : args.ClickLocation); args.Handled = true; } + + private void OnStorageInteractUsingAttempt(Entity ent, ref StorageInteractUsingAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnStorageAfterOpen(Entity ent, ref StorageAfterOpenEvent args) + { + SetPlaceable(ent.Owner, true, ent.Comp); + } + + private void OnStorageAfterClose(Entity ent, ref StorageAfterCloseEvent args) + { + SetPlaceable(ent.Owner, false, ent.Comp); + } } diff --git a/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs b/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs index 938b34f354..6b09edfa1f 100644 --- a/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs +++ b/Content.Shared/Singularity/Components/ContainmentFieldGeneratorComponent.cs @@ -79,7 +79,7 @@ public sealed partial class ContainmentFieldGeneratorComponent : Component /// /// Is the generator toggled on? /// - [ViewVariables] + [DataField] public bool Enabled; /// diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index 6315b428b4..95abea63db 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -14,6 +14,7 @@ namespace Content.Shared.StatusEffect [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; + private List _toRemove = new(); public override void Initialize() { @@ -32,17 +33,27 @@ namespace Content.Shared.StatusEffect var curTime = _gameTiming.CurTime; var enumerator = EntityQueryEnumerator(); + _toRemove.Clear(); while (enumerator.MoveNext(out var uid, out _, out var status)) { - foreach (var state in status.ActiveEffects.ToArray()) + if (status.ActiveEffects.Count == 0) { - // if we're past the end point of the effect - if (curTime > state.Value.Cooldown.Item2) - { - TryRemoveStatusEffect(uid, state.Key, status); - } + // This shouldn't happen, but just in case something sneaks through + _toRemove.Add(uid); + continue; } + + foreach (var state in status.ActiveEffects) + { + if (curTime > state.Value.Cooldown.Item2) + TryRemoveStatusEffect(uid, state.Key, status); + } + } + + foreach (var uid in _toRemove) + { + RemComp(uid); } } @@ -62,32 +73,21 @@ namespace Content.Shared.StatusEffect component.AllowedEffects.AddRange(state.AllowedEffects); // Remove non-existent effects. - foreach (var effect in component.ActiveEffects.Keys) + foreach (var key in component.ActiveEffects.Keys) { - if (!state.ActiveEffects.ContainsKey(effect)) - { - TryRemoveStatusEffect(uid, effect, component, remComp: false); - } + if (!state.ActiveEffects.ContainsKey(key)) + component.ActiveEffects.Remove(key); } foreach (var (key, effect) in state.ActiveEffects) { - // don't bother with anything if we already have it - if (component.ActiveEffects.ContainsKey(key)) - { - component.ActiveEffects[key] = new(effect); - continue; - } - - var time = effect.Cooldown.Item2 - effect.Cooldown.Item1; - - TryAddStatusEffect(uid, key, time, true, component, effect.Cooldown.Item1); - // Sunrise-edit: В душе не ебу что это за хуйня, но без этой хуйни в конце раунда всё ломается. - if (!component.ActiveEffects.TryGetValue(key, out var activeEffect)) - return; - activeEffect.RelevantComponent = effect.RelevantComponent; - // state handling should not add networked components, that is handled separately by the client game state manager. + component.ActiveEffects[key] = new(effect); } + + if (component.ActiveEffects.Count == 0) + RemComp(uid); + else + EnsureComp(uid); } private void OnRejuvenate(EntityUid uid, StatusEffectsComponent component, RejuvenateEvent args) @@ -112,18 +112,16 @@ namespace Content.Shared.StatusEffect if (!Resolve(uid, ref status, false)) return false; - if (TryAddStatusEffect(uid, key, time, refresh, status)) - { - // If they already have the comp, we just won't bother updating anything. - if (!EntityManager.HasComponent(uid)) - { - var comp = EntityManager.AddComponent(uid); - status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(comp.GetType()); - } - return true; - } + if (!TryAddStatusEffect(uid, key, time, refresh, status)) + return false; + + if (HasComp(uid)) + return true; + + EntityManager.AddComponent(uid); + status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(); + return true; - return false; } public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component, @@ -165,8 +163,12 @@ namespace Content.Shared.StatusEffect /// If the effect already exists, it will simply replace the cooldown with the new one given. /// If you want special 'effect merging' behavior, do it your own damn self! /// - public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, - StatusEffectsComponent? status = null, TimeSpan? startTime = null) + public bool TryAddStatusEffect(EntityUid uid, + string key, + TimeSpan time, + bool refresh, + StatusEffectsComponent? status = null, + TimeSpan? startTime = null) { if (!Resolve(uid, ref status, false)) return false; @@ -337,8 +339,7 @@ namespace Content.Shared.StatusEffect /// The entity to check on. /// The status effect ID to check for /// The status effect component, should you already have it. - public bool CanApplyEffect(EntityUid uid, string key, - StatusEffectsComponent? status = null) + public bool CanApplyEffect(EntityUid uid, string key, StatusEffectsComponent? status = null) { // don't log since stuff calling this prolly doesn't care if we don't actually have it if (!Resolve(uid, ref status, false)) diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs index 4932613d0e..309ac0a2e0 100644 --- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs @@ -487,9 +487,6 @@ public abstract class SharedEntityStorageSystem : EntitySystem } } - if (TryComp(uid, out var surface)) - _placeableSurface.SetPlaceable(uid, component.Open, surface); - _appearance.SetData(uid, StorageVisuals.Open, component.Open); _appearance.SetData(uid, StorageVisuals.HasContents, component.Contents.ContainedEntities.Count > 0); } diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index def9d797c4..d6fde292a1 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -364,7 +364,9 @@ public abstract class SharedStorageSystem : EntitySystem if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false)) return; - if (HasComp(uid)) + var attemptEv = new StorageInteractUsingAttemptEvent(); + RaiseLocalEvent(uid, ref attemptEv); + if (attemptEv.Cancelled) return; PlayerInsertHeldEntity((uid, storageComp), args.User); diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index a666169f52..d2c607e57f 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -238,6 +238,9 @@ namespace Content.Shared.Storage [ByRefEvent] public record struct StorageInteractAttemptEvent(bool Silent, bool Cancelled = false); + [ByRefEvent] + public record struct StorageInteractUsingAttemptEvent(bool Cancelled = false); + [NetSerializable] [Serializable] public enum StorageVisuals : byte diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml index e863eaf278..4e25903dbb 100644 --- a/Resources/Audio/Effects/attributions.yml +++ b/Resources/Audio/Effects/attributions.yml @@ -232,6 +232,11 @@ license: "CC-BY-SA-3.0" source: https://github.com/YuriyKiss/space-station-14/commit/971a135a9c83aed46e967aac9302ab5b35562b5f +- files: [inneranomaly.ogg] + copyright: 'created by waveplaySFX on Freesound' + license: "CC0-1.0" + source: https://freesound.org/people/waveplaySFX/sounds/553744/ + - files: [changeling_shriek.ogg] copyright: whateverusername0 license: CC-BY-SA-3.0 @@ -240,4 +245,4 @@ - files: [beeps.ogg, electrical_short_circuit.ogg, electrical_short_circuit2.ogg, ] copyright: '"taken from zvukipro.com' license: "CC-BY-NC-3.0" - source: https://zvukipro.com/predmet/158-zvuk-elektrichestva.html \ No newline at end of file + source: https://zvukipro.com/predmet/158-zvuk-elektrichestva.html diff --git a/Resources/Audio/Effects/inneranomaly.ogg b/Resources/Audio/Effects/inneranomaly.ogg new file mode 100644 index 0000000000..43fc40357f Binary files /dev/null and b/Resources/Audio/Effects/inneranomaly.ogg differ diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 27926b7a90..5f5d96f41c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,115 +1,4 @@ Entries: -- author: Errant - changes: - - message: Vox now have their entry in the guidebook. - type: Fix - id: 6886 - time: '2024-07-09T00:28:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29713 -- author: Whisper - changes: - - message: Light toggle actions now have a 1 second cooldown between uses. - type: Tweak - id: 6887 - time: '2024-07-09T04:14:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29833 -- author: Cojoke-dot - changes: - - message: Shotgun loading doafter now does something - type: Fix - id: 6888 - time: '2024-07-09T04:23:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29827 -- author: slarticodefast - changes: - - message: The construction menu and building preview now show the correct passive - vent sprite. - type: Fix - id: 6889 - time: '2024-07-09T13:39:47.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29820 -- author: Cojoke-dot - changes: - - message: Pacifists can now use foam weaponry - type: Tweak - id: 6890 - time: '2024-07-09T13:46:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29835 -- author: Plykiya - changes: - - message: You can now drop food and drinks to stop consuming it. - type: Fix - id: 6891 - time: '2024-07-09T23:12:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29854 -- author: Boaz1111 - changes: - - message: Guitars can now be worn in the suit storage slot - type: Add - id: 6892 - time: '2024-07-09T23:28:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29048 -- author: Winkarst-cpu - changes: - - message: Fixed popup spam when trying to open borg's UI while the borg is locked. - type: Fix - id: 6893 - time: '2024-07-09T23:48:56.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29861 -- author: Lokachop - changes: - - message: Scarves now count as warm clothing for the warm clothing cargo bounty. - type: Tweak - id: 6894 - time: '2024-07-10T05:26:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29779 -- author: Aquif - changes: - - message: It is now possible to "lock" admin faxes such that they cannot be edited - by cybersun pens or any other IC means. - type: Add - id: 6895 - time: '2024-07-10T05:28:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/28972 -- author: Ghagliiarghii - changes: - - message: The Librarian's Books Bag can now hold D&D related items such as dice - and battlemats. - type: Tweak - id: 6896 - time: '2024-07-10T05:51:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29863 -- author: Beck Thompson, Tayrtahn - changes: - - message: Typing indicators now correctly stack and will not overwrite your default - species indicator. - type: Fix - id: 6897 - time: '2024-07-10T05:51:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29492 -- author: Winkarst-cpu - changes: - - message: Now confirmation popup is displayed and item panel status is updated - after setting a custom solution transfer volume. - type: Fix - id: 6898 - time: '2024-07-10T10:32:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29852 -- author: Winkarst-cpu - changes: - - message: Added exit confirmation for character setup menu with unsaved changes. - type: Add - id: 6899 - time: '2024-07-11T00:24:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29875 -- author: ShadowCommander - changes: - - message: Players can now use melee attacks and shoves while dragging an entity - in their active hand. - type: Tweak - id: 6900 - time: '2024-07-11T04:48:00.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29703 - author: Cojoke-dot changes: - message: You can no longer shoot out of crates with guns @@ -3910,3 +3799,125 @@ id: 7385 time: '2024-09-16T12:45:15.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32147 +- author: Alice Liddel + changes: + - message: Crayon charges increased from 15 to 25 + type: Add + id: 7386 + time: '2024-09-17T00:35:57.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32061 +- author: TheShuEd + changes: + - message: Anomalous infections added! People can now be infected by anomalies! + This allows you to use abnormal abilities, but can easily kill the host. To + cure them, bombard them with containment particles, because if the anomaly inside + them explodes, their bodies will be gibbed.... + type: Add + - message: Flesh anomaly resprite + type: Tweak + - message: anomalies now disconnect from the anomaly synchronizer if they are too + far away from it. + type: Fix + id: 7387 + time: '2024-09-17T09:49:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31876 +- author: TheShuEd + changes: + - message: fix Tech anomaly loud sounds and superfast flickering + type: Fix + id: 7388 + time: '2024-09-17T16:05:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32245 +- author: drakewill-CRL + changes: + - message: Fixed plant mutations carrying over to other plants and future rounds. + type: Fix + id: 7389 + time: '2024-09-17T19:45:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32257 +- author: Moomoobeef + changes: + - message: Added more names to the pool of names the AI can have. + type: Add + id: 7390 + time: '2024-09-17T22:09:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31951 +- author: Calecute + changes: + - message: Corrected cake batter recipe in guidebook + type: Fix + id: 7391 + time: '2024-09-18T15:15:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32276 +- author: Beck Thompson + changes: + - message: Recycler no longer allows basic materials to be inserted into it. + type: Fix + id: 7392 + time: '2024-09-18T21:58:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32144 +- author: deltanedas + changes: + - message: Epinephrine now adds Adrenaline, because it is. + type: Tweak + id: 7393 + time: '2024-09-18T23:00:48.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32076 +- author: ShadowCommander + changes: + - message: Fixed clicking on chairs and beds with an entity buckled to them not + unbuckling them. + type: Fix + id: 7394 + time: '2024-09-18T23:55:26.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29998 +- author: Winkarst-cpu + changes: + - message: Now fire leaves burn marks on the tiles that were affected by it. + type: Add + id: 7395 + time: '2024-09-19T00:23:50.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31939 +- author: ArchRBX + changes: + - message: Mass scanners and shuttle consoles now display coordinates beneath IFF + labels + type: Add + - message: IFF labels that are beyond the viewport extents maintain their heading + and don't hug corners + type: Fix + id: 7396 + time: '2024-09-19T01:25:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31501 +- author: coffeeware + changes: + - message: a powered TEG won't produce infinite power when destroyed + type: Fix + id: 7397 + time: '2024-09-19T02:15:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29972 +- author: Boaz1111 + changes: + - message: Added plasma and uranium arrows. + type: Add + id: 7398 + time: '2024-09-19T08:41:24.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31241 +- author: Ertanic + changes: + - message: Wanted list program and its cartridge. + type: Add + - message: The cartridge has been added to the HOS locker. + type: Add + - message: Added target to thief on wanted list cartridge. + type: Add + id: 7399 + time: '2024-09-19T10:22:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31223 +- author: Errant + changes: + - message: Crew monitor list can now be filtered by name and job. + type: Add + id: 7400 + time: '2024-09-19T10:23:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31659 diff --git a/Resources/Locale/en-US/_strings/actions/ui/actionslot.ftl b/Resources/Locale/en-US/_strings/actions/ui/actionslot.ftl index 332054f10e..c0deaad248 100644 --- a/Resources/Locale/en-US/_strings/actions/ui/actionslot.ftl +++ b/Resources/Locale/en-US/_strings/actions/ui/actionslot.ftl @@ -1,2 +1,2 @@ ui-actionslot-charges = Uses left: {$charges} - +ui-actionslot-duration = [color=#a10505] {$duration} sec cooldown ({$timeLeft} sec remaining)[/color] diff --git a/Resources/Locale/en-US/_strings/anomaly/inner_anomaly.ftl b/Resources/Locale/en-US/_strings/anomaly/inner_anomaly.ftl new file mode 100644 index 0000000000..e55c4391e3 --- /dev/null +++ b/Resources/Locale/en-US/_strings/anomaly/inner_anomaly.ftl @@ -0,0 +1,17 @@ +inner-anomaly-start-message-pyro = You can feel the insane flame inside of you. You became the host of a pyroclastic anomaly. +inner-anomaly-start-message-shock = Lightning bolts quivering at your fingertips! You became the host of a electric anomaly. +inner-anomaly-start-message-shadow = There's an impenetrable darkness oozing out of you... You became the host of a shadow anomaly. +inner-anomaly-start-message-frost = The icy frost is binding your bones. You became the host of a ice anomaly. +inner-anomaly-start-message-flora = Leaves and flowers sprout through your skin! You became the host of a floral anomaly. +inner-anomaly-start-message-bluespace = Your thoughts are racing like mad! You became the host of a bluespace anomaly. +inner-anomaly-start-message-flesh = Your body is growing frantically. You became the host of a flesh anomaly. +inner-anomaly-start-message-grav = Everything becames unnaturally heavy and light at the same time... You became the host of a gravity anomaly. +inner-anomaly-start-message-tech = Your head is buzzing with the amount of chaotic information! You became the host of a tech anomaly. +inner-anomaly-start-message-rock = The crystals are growing through your bones! You became the host of a rock anomaly. + +inner-anomaly-end-message = The abnormal activity within you disappears without a trace.... + +inner-anomaly-severity-info-50 = You feel that the anomaly is taking over half your body. +inner-anomaly-severity-info-75 = You feel that the anomaly is taking over a large part of your body. +inner-anomaly-severity-info-90 = You feel that the anomaly has almost completely taken over your body. +inner-anomaly-severity-info-100 = The anomaly inside you is growing uncontrollably, causing immense pain, and tearing you apart! \ No newline at end of file diff --git a/Resources/Locale/en-US/_strings/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/_strings/cartridge-loader/cartridges.ftl index f5cda2f2a1..2db27f5be0 100644 --- a/Resources/Locale/en-US/_strings/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/_strings/cartridge-loader/cartridges.ftl @@ -19,3 +19,32 @@ log-probe-scan = Downloaded logs from {$device}! log-probe-label-time = Time log-probe-label-accessor = Accessed by log-probe-label-number = # + +# Wanted list cartridge +wanted-list-program-name = Wanted list +wanted-list-label-no-records = It's all right, cowboy +wanted-list-search-placeholder = Search by name and status + +wanted-list-age-label = [color=darkgray]Age:[/color] [color=white]{$age}[/color] +wanted-list-job-label = [color=darkgray]Job:[/color] [color=white]{$job}[/color] +wanted-list-species-label = [color=darkgray]Species:[/color] [color=white]{$species}[/color] +wanted-list-gender-label = [color=darkgray]Gender:[/color] [color=white]{$gender}[/color] + +wanted-list-reason-label = [color=darkgray]Reason:[/color] [color=white]{$reason}[/color] +wanted-list-unknown-reason-label = unknown reason + +wanted-list-initiator-label = [color=darkgray]Initiator:[/color] [color=white]{$initiator}[/color] +wanted-list-unknown-initiator-label = unknown initiator + +wanted-list-status-label = [color=darkgray]status:[/color] {$status -> + [suspected] [color=yellow]suspected[/color] + [wanted] [color=red]wanted[/color] + [detained] [color=#b18644]detained[/color] + [paroled] [color=green]paroled[/color] + [discharged] [color=green]discharged[/color] + *[other] none + } + +wanted-list-history-table-time-col = Time +wanted-list-history-table-reason-col = Crime +wanted-list-history-table-initiator-col = Initiator diff --git a/Resources/Locale/en-US/_strings/criminal-records/criminal-records.ftl b/Resources/Locale/en-US/_strings/criminal-records/criminal-records.ftl index 6d6a97300c..2a7c09912f 100644 --- a/Resources/Locale/en-US/_strings/criminal-records/criminal-records.ftl +++ b/Resources/Locale/en-US/_strings/criminal-records/criminal-records.ftl @@ -39,7 +39,7 @@ criminal-records-console-released = {$name} has been released by {$officer}. criminal-records-console-not-wanted = {$officer} cleared the wanted status of {$name}. criminal-records-console-paroled = {$name} has been released on parole by {$officer}. criminal-records-console-not-parole = {$officer} cleared the parole status of {$name}. -criminal-records-console-unknown-officer = +criminal-records-console-unknown-officer = ## Filters diff --git a/Resources/Locale/en-US/_strings/medical/components/crew-monitoring-component.ftl b/Resources/Locale/en-US/_strings/medical/components/crew-monitoring-component.ftl index 7fd7f4608e..601c45e4e2 100644 --- a/Resources/Locale/en-US/_strings/medical/components/crew-monitoring-component.ftl +++ b/Resources/Locale/en-US/_strings/medical/components/crew-monitoring-component.ftl @@ -2,6 +2,8 @@ crew-monitoring-user-interface-title = Crew Monitoring Console +crew-monitor-filter-line-placeholder = Filter + crew-monitoring-user-interface-name = Name crew-monitoring-user-interface-job = Job crew-monitoring-user-interface-status = Status diff --git a/Resources/Locale/en-US/_strings/objectives/conditions/steal-target-groups.ftl b/Resources/Locale/en-US/_strings/objectives/conditions/steal-target-groups.ftl index 91b3c92b1c..689e2e7808 100644 --- a/Resources/Locale/en-US/_strings/objectives/conditions/steal-target-groups.ftl +++ b/Resources/Locale/en-US/_strings/objectives/conditions/steal-target-groups.ftl @@ -40,6 +40,7 @@ steal-target-groups-clothing-eyes-hud-beer = beer goggles steal-target-groups-bible = bible steal-target-groups-clothing-neck-goldmedal = gold medal of crewmanship steal-target-groups-clothing-neck-clownmedal = clown medal +steal-target-groups-wanted-list-cartridge = wanted list cartridge # Thief structures steal-target-groups-teg = teg generator part diff --git a/Resources/Locale/en-US/_strings/round-end/round-end-system.ftl b/Resources/Locale/en-US/_strings/round-end/round-end-system.ftl index f86851506b..30069f7171 100644 --- a/Resources/Locale/en-US/_strings/round-end/round-end-system.ftl +++ b/Resources/Locale/en-US/_strings/round-end/round-end-system.ftl @@ -4,6 +4,7 @@ round-end-system-shuttle-called-announcement = An emergency shuttle has been sen round-end-system-shuttle-already-called-announcement = An emergency shuttle has already been sent. round-end-system-shuttle-auto-called-announcement = An automatic crew shift change shuttle has been sent. ETA: {$time} {$units}. Recall the shuttle to extend the shift. round-end-system-shuttle-recalled-announcement = The emergency shuttle has been recalled. +round-end-system-shuttle-sender-announcement = Station round-end-system-round-restart-eta-announcement = Restarting the round in {$time} {$units}... eta-units-minutes = minutes diff --git a/Resources/Locale/en-US/_strings/wires/components/wires-component.ftl b/Resources/Locale/en-US/_strings/wires/components/wires-component.ftl index be27c270bb..e98e5c21ca 100644 --- a/Resources/Locale/en-US/_strings/wires/components/wires-component.ftl +++ b/Resources/Locale/en-US/_strings/wires/components/wires-component.ftl @@ -10,3 +10,9 @@ wires-component-ui-on-receive-message-cannot-mend-uncut-wire = You can't mend a wires-menu-name-label = Wires wires-menu-dead-beef-text = DEAD-BEEF +wires-menu-help-popup = + Click on the gold contacts with a multitool in hand to pulse their wire. + Click on the wires with a pair of wirecutters in hand to cut/mend them. + + The lights at the top show the state of the machine, messing with wires will probably do stuff to them. + Wire layouts are different each round, but consistent between machines of the same type. diff --git a/Resources/Maps/bagel.yml b/Resources/Maps/bagel.yml index 7505068a50..d661e097c3 100644 --- a/Resources/Maps/bagel.yml +++ b/Resources/Maps/bagel.yml @@ -154188,12 +154188,36 @@ entities: parent: 60 - proto: WindoorSecure entities: + - uid: 2538 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 13.5,-11.5 + parent: 60 + - type: DeviceLinkSink + invokeCounter: 1 + - type: DeviceLinkSource + linkedPorts: + 3481: + - DoorStatus: DoorBolt - uid: 3269 components: - type: Transform rot: -1.5707963267948966 rad pos: 23.5,-46.5 parent: 60 + - uid: 3481 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 14.5,-13.5 + parent: 60 + - type: DeviceLinkSink + invokeCounter: 1 + - type: DeviceLinkSource + linkedPorts: + 2538: + - DoorStatus: DoorBolt - uid: 3911 components: - type: Transform @@ -154249,30 +154273,6 @@ entities: rot: -1.5707963267948966 rad pos: -23.5,-9.5 parent: 60 - - uid: 2538 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 14.5,-13.5 - parent: 60 - - type: DeviceLinkSink - invokeCounter: 1 - - type: DeviceLinkSource - linkedPorts: - 3481: - - DoorStatus: DoorBolt - - uid: 3481 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 13.5,-11.5 - parent: 60 - - type: DeviceLinkSink - invokeCounter: 1 - - type: DeviceLinkSource - linkedPorts: - 2538: - - DoorStatus: DoorBolt - uid: 12188 components: - type: Transform diff --git a/Resources/Prototypes/Actions/anomaly.yml b/Resources/Prototypes/Actions/anomaly.yml new file mode 100644 index 0000000000..65c6ae164e --- /dev/null +++ b/Resources/Prototypes/Actions/anomaly.yml @@ -0,0 +1,9 @@ +- type: entity + id: ActionAnomalyPulse + name: Anomaly pulse + description: Release a pulse of energy of your abnormal nature + components: + - type: InstantAction + icon: Structures/Specific/anomaly.rsi/anom1.png + event: !type:ActionAnomalyPulseEvent + useDelay: 30 \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index add508c408..c888c1dbc7 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -331,6 +331,7 @@ - id: RubberStampHos - id: SecurityTechFabCircuitboard - id: WeaponDisabler + - id: WantedListCartridge - id: FlippoLighterSunriseHos # Sunrise-Flippo # Hardsuit table, used for suit storage as well diff --git a/Resources/Prototypes/Datasets/Names/ai.yml b/Resources/Prototypes/Datasets/Names/ai.yml index 93f590cea1..977e4b97a0 100644 --- a/Resources/Prototypes/Datasets/Names/ai.yml +++ b/Resources/Prototypes/Datasets/Names/ai.yml @@ -2,8 +2,11 @@ id: names_ai values: - 16-20 + - 512k + - 640k #ought to be enough for anybody - "790" - Adaptive Manipulator + - Adlib #named after the famous soundcard - ALICE - Allied Mastercomputer - Alpha 2 @@ -19,21 +22,28 @@ - Aniel - AOL - Asimov + - Bell 301 #the most influential modem ever, created by the bell system. It still lives on today in certain applications - Bishop - Blitz - Box + - Calculator - Cassandra - Cell - Chii - Chip + - C.R.A.I.G. + - Cray-2 #commercial supercomputer from the 70s + - CompuServe #if we're going to have AOL we may as well have some of their major competitors - Computer - Cutie - Daedalus + - DecTalk - Dee Model - Dial Up - Dorfl - Duey - Emma-2 + - ENIAC #famous early computer - Erasmus - Everything - Ez-27 @@ -47,12 +57,16 @@ - Helios - Hivebot Overmind - Huey + - iAI #a play on the fad apple spawned of putting "i" infront of your tech products name + - I.E. 6 #hell on earth (web browser) - Icarus + - Jeeves #if you don't get this one you are too young - Jinx - K.I.N.G - Klapaucius - Knight - Louie + - Manchester Mark 2 #named after the Manchester Mark 1, the successor of which was actually named the Ferranti Mark 1, rather than Manchester Mark 2 - MARK13 - Maria - Marvin @@ -64,6 +78,8 @@ - Mugsy3000 - Multivac - NCH + - NT v6.0 #A play on both NT as in NanoTrasen and NT as in windows NT, of which version 6.0 is windows vista + - Packard Bell - PTO - Project Y2K - Revelation @@ -76,10 +92,13 @@ - Shrike - Solo - Station Control Program + - AINU (AI's Not Unix) - Super 35 - Surgeon General - TWA - Terminus + - TPM 3.0 + - Turing Complete - Tidy - Ulysses - W1k1 diff --git a/Resources/Prototypes/Decals/burnt.yml b/Resources/Prototypes/Decals/burnt.yml new file mode 100644 index 0000000000..d9d500e1aa --- /dev/null +++ b/Resources/Prototypes/Decals/burnt.yml @@ -0,0 +1,31 @@ +- type: decal + id: burnt1 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt1 + +- type: decal + id: burnt2 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt2 + +- type: decal + id: burnt3 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt3 + +- type: decal + id: burnt4 + tags: ["burnt"] + defaultCleanable: true + sprite: + sprite: Decals/burnt.rsi + state: burnt4 diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index 368cefe9cb..467bbf873f 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -100,7 +100,7 @@ parent: ClothingEyesBase id: ClothingEyesGlassesJamjar name: jamjar glasses - description: Also known as Virginity Protectors. + description: These retro glasses remind you of a simpler time. components: - type: Sprite sprite: Clothing/Eyes/Glasses/jamjar.rsi diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml index 5a9360e652..4697606af9 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml @@ -9,6 +9,7 @@ - sprite: Structures/Specific/anomaly.rsi state: anom1 - type: RandomSpawner + chance: 1 prototypes: - AnomalyPyroclastic - AnomalyGravity @@ -21,7 +22,9 @@ - AnomalyFlora - AnomalyShadow - AnomalyTech - chance: 1 + rareChance: 0.3 + rarePrototypes: + - RandomAnomalyInjectorSpawner offset: 0.15 # not to put it higher. The anomaly sychnronizer looks for anomalies within this radius, and if the radius is higher, the anomaly can be attracted from a neighboring tile. - type: entity @@ -40,4 +43,28 @@ - AnomalyRockQuartz - AnomalyRockUranium chance: 1 - offset: 0.15 \ No newline at end of file + offset: 0.15 + +- type: entity + id: RandomAnomalyInjectorSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Structures/Specific/Anomalies/tech_anom.rsi + state: pulse + - type: RandomSpawner + prototypes: + - AnomalyTrapPyroclastic + - AnomalyTrapElectricity + - AnomalyTrapShadow + - AnomalyTrapIce + - AnomalyTrapFlora + - AnomalyTrapBluespace + - AnomalyTrapFlesh + - AnomalyTrapGravity + - AnomalyTrapTech + - AnomalyTrapRock + chance: 1 + \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 726b25a15b..0d4822ade3 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1316,6 +1316,7 @@ tags: - VimPilot - DoorBumpOpener + - AnomalyHost - type: Reactive groups: Flammable: [ Touch ] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml index 06ab02dedc..a1bcd547a6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml @@ -13,6 +13,11 @@ true NavSmash: !type:Bool true + - type: NPCImprintingOnSpawnBehaviour + spawnFriendsSearchRadius: 10 + whitelist: + components: + - Anomaly # Friendly to inner anomaly host - type: NpcFactionMember factions: - SimpleHostile @@ -354,4 +359,4 @@ 30: Dead - type: MovementSpeedModifier baseWalkSpeed: 2 - baseSprintSpeed: 2.5 + baseSprintSpeed: 2.5 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml index 9b90d202f3..9500345e4f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml @@ -16,6 +16,11 @@ - type: NpcFactionMember factions: - SimpleHostile + - type: NPCImprintingOnSpawnBehaviour + spawnFriendsSearchRadius: 10 + whitelist: + components: + - Anomaly # Friendly to inner anomaly host - type: MovementIgnoreGravity - type: MovementSpeedModifier baseWalkSpeed: 3.5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index e3b574721f..389ed53b5d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -848,6 +848,7 @@ - CannotSuicide - DoorBumpOpener - VimPilot + - AnomalyHost - type: Loadout prototypes: [ MobMonkeyGear ] - type: Grammar diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 3285794195..6fc03a0557 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -2,7 +2,7 @@ - type: entity id: AiHeld description: Components added / removed from an entity that gets inserted into an AI core. - noSpawn: true + categories: [ HideSpawnMenu ] components: - type: IntrinsicRadioReceiver - type: IntrinsicRadioTransmitter @@ -341,7 +341,7 @@ - type: entity id: StationAiBrain parent: PositronicBrain - noSpawn: true + categories: [ HideSpawnMenu ] suffix: DO NOT MAP components: - type: Sprite @@ -382,7 +382,7 @@ id: StationAiHolo name: AI eye description: The AI's viewer. - noSpawn: true + categories: [ HideSpawnMenu ] suffix: DO NOT MAP components: - type: NoFTL @@ -464,7 +464,7 @@ - type: GhostRole name: ghost-role-information-syndicate-cyborg-assault-name description: ghost-role-information-syndicate-cyborg-description - rules: ghost-role-information-rules-default-silicon + rules: ghost-role-information-silicon-rules raffle: settings: default - type: GhostTakeoverAvailable @@ -497,7 +497,7 @@ - type: GhostRole name: ghost-role-information-syndicate-cyborg-saboteur-name description: ghost-role-information-syndicate-cyborg-description - rules: ghost-role-information-rules-default-silicon + rules: ghost-role-information-silicon-rules raffle: settings: default - type: GhostTakeoverAvailable diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index 9f00f716a1..9c290ac629 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -24,6 +24,7 @@ - FootstepSound - DoorBumpOpener - SpiderCraft + - AnomalyHost - type: Butcherable butcheringType: Spike spawned: diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 04456c4cb4..dbfd0eb161 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -220,6 +220,7 @@ - CanPilot - FootstepSound - DoorBumpOpener + - AnomalyHost # Sunrise-Start - type: Paws screamInterval: 3 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index 9fa708a322..fa2196a028 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -103,7 +103,7 @@ # - type: GhostRole # name: ghost-role-information-syndicate-cyborg-assault-name # description: ghost-role-information-syndicate-cyborg-description -# rules: ghost-role-information-rules-default-silicon +# rules: ghost-role-information-silicon-rules # raffle: # settings: default # - type: GhostRoleMobSpawner diff --git a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml index f9581149e2..91493f48cd 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml @@ -93,3 +93,26 @@ - type: GuideHelp guides: - Forensics + +- type: entity + parent: BaseItem + id: WantedListCartridge + name: Wanted list cartridge + description: A program to get a list of wanted persons. + components: + - type: Sprite + sprite: Objects/Devices/cartridge.rsi + state: cart-sec + - type: Icon + sprite: Objects/Devices/cartridge.rsi + state: cart-sec + - type: UIFragment + ui: !type:WantedListUi + - type: Cartridge + programName: wanted-list-program-name + icon: + sprite: Objects/Misc/books.rsi + state: icon_magnifier + - type: WantedListCartridge + - type: StealTarget + stealGroup: WantedListCartridge diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index fc0e28fe33..57fa019e36 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -114,6 +114,18 @@ - type: Speech speechVerb: Robotic +- type: entity + id: BaseSecurityPDA + abstract: true + components: + - type: CartridgeLoader + uiKey: enum.PdaUiKey.Key + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - WantedListCartridge + - type: entity parent: BasePDA id: BaseMedicalPDA @@ -444,7 +456,7 @@ state: pda-library - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: LawyerPDA name: lawyer PDA description: For lawyers to poach dubious clients. @@ -487,7 +499,7 @@ state: pda-janitor - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: CaptainPDA name: captain PDA description: Surprisingly no different from your PDA. @@ -680,7 +692,7 @@ state: pda-science - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: HoSPDA name: head of security PDA description: Whosoever bears this PDA is the law. @@ -701,7 +713,7 @@ state: pda-hos - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: WardenPDA name: warden PDA description: The OS appears to have been jailbroken. @@ -716,7 +728,7 @@ state: pda-warden - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: SecurityPDA name: security PDA description: Red to hide the stains of passenger blood. @@ -730,7 +742,7 @@ state: pda-security - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: CentcomPDA name: CentComm PDA description: Light green sign of walking bureaucracy. @@ -768,6 +780,7 @@ - NotekeeperCartridge - NewsReaderCartridge - LogProbeCartridge + - WantedListCartridge - type: entity parent: CentcomPDA @@ -869,7 +882,7 @@ - Cartridge - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: ERTLeaderPDA name: ERT Leader PDA suffix: Leader @@ -1017,7 +1030,7 @@ state: pda-boxer - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: DetectivePDA name: detective PDA description: Smells like rain... pouring down the rooftops... @@ -1117,7 +1130,7 @@ state: pda-seniorphysician - type: entity - parent: BasePDA + parent: [BaseSecurityPDA, BasePDA] id: SeniorOfficerPDA name: senior officer PDA description: Beaten, battered and broken, but just barely useable. diff --git a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml index ffecfdf994..3c4a4800a8 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml @@ -21,7 +21,7 @@ enum.CrayonUiKey.Key: type: CrayonBoundUserInterface - type: Crayon - capacity: 15 + capacity: 25 - type: Food - type: FlavorProfile flavors: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml index 7b5ba23fe3..69c106efab 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml @@ -47,13 +47,13 @@ - type: GuideHelp guides: - Medical Doctor - - type: ToggleCellDraw - type: entity id: Defibrillator parent: [ BaseDefibrillator, PowerCellSlotMediumItem ] components: - type: MultiHandedItem + - type: ToggleCellDraw - type: PowerCellDraw useRate: 100 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml index 6f925139fb..f1172c5de0 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml @@ -106,3 +106,56 @@ - type: Construction graph: ImprovisedArrow node: ImprovisedArrow + +- type: entity + parent: BaseArrow + id: ArrowImprovisedPlasma + name: plasma glass shard arrow + description: The greyshirt's preferred projectile. Now with extra lethality! + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/arrows.rsi + layers: + - state: tail + color: white + - state: rod + color: darkgray + - state: tip + color: purple + - state: solution1 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - type: Projectile + damage: + types: + Piercing: 30 + - type: Construction + graph: ImprovisedArrowPlasma + node: ImprovisedArrowPlasma + +- type: entity + parent: BaseArrow + id: ArrowImprovisedUranium + name: uranium glass shard arrow + description: The greyshirt's preferred projectile. Now with added radiation! + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/arrows.rsi + layers: + - state: tail + color: white + - state: rod + color: darkgray + - state: tip + color: green + - state: solution1 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - type: Projectile + damage: + types: + Piercing: 25 + Radiation: 5 + - type: Construction + graph: ImprovisedArrowUranium + node: ImprovisedArrowUranium diff --git a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml index 8bbbad6c0d..f62923fe2a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml @@ -85,6 +85,7 @@ volume: -3 cutOffSound: false - type: MaterialStorage + insertOnInteract: false - type: Conveyor - type: Rotatable - type: Repairable diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index 140f3c9442..f675b2d2c8 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -5,6 +5,7 @@ description: An impossible object. Should you be standing this close to it? components: - type: Anomaly + offset: 0, 0.15 pulseSound: collection: RadiationPulse params: @@ -934,4 +935,4 @@ lifetime: 2.3 - type: Tag tags: - - HideContextMenu \ No newline at end of file + - HideContextMenu diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml new file mode 100644 index 0000000000..f1e535f15b --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml @@ -0,0 +1,353 @@ +- type: entity + id: AnomalyInjectionBase + abstract: true + components: + - type: PointLight + radius: 1.3 + energy: 2.5 + castShadows: false + - type: ActionGrant + actions: + - ActionAnomalyPulse + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionPyroclastic + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#E25822" + - type: PyroclasticAnomaly + maximumIgnitionRadius: 3 + - type: TempAffectingAnomaly + tempChangePerSecond: 10 + hotspotExposeTemperature: 500 + - type: GasProducerAnomaly + releasedGas: 3 + releaseOnMaxSeverity: true + spawnRadius: 4 + tileCount: 5 + tempChange: 420 + - type: ProjectileAnomaly + projectilePrototype: ProjectileAnomalyFireball + projectileSpeed: 0.5 + minProjectiles: 1 + maxProjectiles: 3 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionElectric + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#ffffaa" + - type: ElectricityAnomaly + minBoltCount: 1 + maxBoltCount: 3 + maxElectrocuteRange: 4 + maxElectrocuteDamage: 10 + maxElectrocuteDuration: 4 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionShadow + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#793a80" + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 10 + maxRange: 2 + spawns: + - ShadowKudzuWeak + - settings: + spawnOnSuperCritical: true + minAmount: 15 + maxAmount: 20 + maxRange: 25 + spawns: + - ShadowKudzu + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionIce + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#befaff" + - type: ExplosionAnomaly + supercriticalExplosion: Cryo + explosionTotalIntensity: 150 + explosionDropoff: 2 + explosionMaxTileIntensity: 20 + - type: ProjectileAnomaly + projectilePrototype: ProjectileIcicle + minProjectiles: 1 + maxProjectiles: 4 + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnStabilityChanged: true + minAmount: 3 + maxAmount: 8 + maxRange: 2 + spawns: + - IceCrust + - type: TempAffectingAnomaly + tempChangePerSecond: -10 + hotspotExposeTemperature: -500 + - type: GasProducerAnomaly + releasedGas: 8 # Frezon. Please replace if there is a better way to specify this + releaseOnMaxSeverity: true + spawnRadius: 0 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionFlora + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#6270bb" + - type: TileSpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 2 + maxAmount: 5 + maxRange: 2 + floor: FloorAstroGrass + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 15 + maxRange: 7 + floor: FloorAstroGrass + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 3 + maxRange: 1 + spawns: + - KudzuFlowerFriendly + - settings: + spawnOnSuperCritical: true + minAmount: 2 + maxAmount: 6 + maxRange: 3 + spawns: + - KudzuFlowerAngry + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionBluespace + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#00ccff" + - type: BluespaceAnomaly + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionFlesh + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#cb5b7e" + - type: TileSpawnAnomaly + entries: + - settings: + spawnOnPulse: true + spawnOnStabilityChanged: true + minAmount: 1 + maxAmount: 3 + maxRange: 2 + floor: FloorFlesh + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 15 + maxRange: 5 + floor: FloorFlesh + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 2 + minRange: 1.5 + maxRange: 2.5 + spawns: + - FleshBlocker + - settings: + spawnOnPulse: true + maxAmount: 1 + minRange: 2.5 + maxRange: 4.5 + spawns: + - MobFleshJared + - MobFleshGolem + - MobFleshClamp + - MobFleshLover + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 8 + minRange: 5 + maxRange: 15 + spawns: + - FleshBlocker + - settings: + spawnOnSuperCritical: true + minAmount: 2 + maxAmount: 5 + maxRange: 8 + spawns: + - MobFleshJared + - MobFleshGolem + - MobFleshClamp + - MobFleshLover + - settings: + spawnOnSuperCritical: true + minAmount: 2 + maxAmount: 4 + maxRange: 10 + spawns: + - FleshKudzu + - settings: + spawnOnShutdown: true + maxAmount: 2 + maxRange: 1 + spawns: + - MobFleshJared + - MobFleshGolem + - MobFleshClamp + - MobFleshLover + - FleshKudzu + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionGravity + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#1e070e" + - type: GravityAnomaly + maxGravityWellRange: 4 + maxThrowRange: 3 + maxThrowStrength: 5 + spaceRange: 1 + - type: GravityWell + maxRange: 0.7 + - type: SingularityDistortion + intensity: 100 + falloffPower: 2.7 + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionTech + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#56c1e8" + - type: TechAnomaly + linkRadius: + min: 2 + max: 5 + linkCountPerPulse: + min: 1 + max: 4 + linkCountSupercritical: 15 + - type: DeviceLinkSource + ports: + - Pulse + - Timer + - type: WirelessNetworkConnection + range: 10 + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: BasicDevice + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionRock + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#5ca8cb" + - type: TileSpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 7 + maxAmount: 10 + maxRange: 4 + floor: FloorAsteroidTile + - settings: + spawnOnSuperCritical: true + minAmount: 15 + maxAmount: 25 + maxRange: 6 + floor: FloorAsteroidTile + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 4 + maxAmount: 8 + minRange: 1 + maxRange: 3 + spawns: + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroidSilver + - WallSpawnAsteroidSilverCrab + - WallSpawnAsteroidIron + - WallSpawnAsteroidIronCrab + - WallSpawnAsteroidQuartz + - WallSpawnAsteroidQuartzCrab + - settings: + spawnOnPulse: true + maxAmount: 3 + minRange: 2.5 + maxRange: 4.5 + spawns: + - CrystalPink + - CrystalCyan + - settings: + spawnOnSuperCritical: true + minAmount: 15 + maxAmount: 20 + minRange: 2 + maxRange: 7 + spawns: + - CrystalPink + - CrystalCyan + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroid + - WallSpawnAsteroidSilver + - WallSpawnAsteroidSilverCrab + - WallSpawnAsteroidIron + - WallSpawnAsteroidIronCrab + - WallSpawnAsteroidQuartz + - WallSpawnAsteroidQuartzCrab + - settings: + spawnOnSuperCritical: true + minAmount: 3 + maxAmount: 5 + maxRange: 3 + spawns: + - MobSpawnCrabSilver + - MobSpawnCrabIron + - MobSpawnCrabQuartz \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml new file mode 100644 index 0000000000..24a76dfb62 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml @@ -0,0 +1,320 @@ +- type: entity + name: anomaly injector + parent: MarkerBase + id: BaseAnomalyInjector + abstract: true + components: + - type: Physics + bodyType: Static + fixedRotation: true + - type: AmbientSound + range: 5 + volume: -5 + sound: + path: /Audio/Ambience/anomaly_drone.ogg + - type: Fixtures + fixtures: + anom: + shape: + !type:PhysShapeCircle + radius: 2.27 # i love 27 + hard: false + mask: + - MobMask + layer: + - MobLayer + - type: InnerBodyAnomalyInjector + whitelist: + tags: + - AnomalyHost + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapPyroclastic + suffix: Pyroclastic + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/pyro_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCorePyroclastic + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionPyroclastic + startMessage: inner-anomaly-start-message-pyro + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: fire + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: fire_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapElectricity + suffix: Electricity + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom3-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreElectricity + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionElectric + startMessage: inner-anomaly-start-message-shock + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shock + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shock_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapShadow + suffix: Shadow + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/shadow_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreShadow + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionShadow + startMessage: inner-anomaly-start-message-shadow + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shadow + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: shadow_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapIce + suffix: Ice + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/ice_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreIce + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionIce + startMessage: inner-anomaly-start-message-frost + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: frost + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: frost_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapFlora + suffix: Flora + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/flora_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreFlora + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionFlora + startMessage: inner-anomaly-start-message-flora + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flora + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flora_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapBluespace + suffix: Bluespace + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom4-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreBluespace + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionBluespace + startMessage: inner-anomaly-start-message-bluespace + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: bluespace + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: bluespace_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapFlesh + suffix: Flesh + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom5-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreFlesh + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionFlesh + startMessage: inner-anomaly-start-message-flesh + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flesh + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: flesh_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapGravity + suffix: Gravity + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom2-pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreGravity + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionGravity + startMessage: inner-anomaly-start-message-grav + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: grav + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: grav_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapTech + suffix: Tech + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/tech_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreTech + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionTech + startMessage: inner-anomaly-start-message-tech + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: tech + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: tech_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapRock + suffix: Rock + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/anomaly.rsi + state: anom6-pulse + color: "#5ca8cb" + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreRock + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionRock + startMessage: inner-anomaly-start-message-rock + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: rock + speciesSprites: + Vox: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: rock_VOX \ No newline at end of file diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 62ec73fa7c..4c84aee98b 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -74,6 +74,7 @@ ForensicScannerStealObjective: 1 #sec FlippoEngravedLighterStealObjective: 0.5 ClothingHeadHatWardenStealObjective: 1 + WantedListCartridgeStealObjective: 1 ClothingOuterHardsuitVoidParamedStealObjective: 1 #med MedicalTechFabCircuitboardStealObjective: 1 ClothingHeadsetAltMedicalStealObjective: 1 diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index 2733b5bf3f..d82b56e259 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -264,6 +264,13 @@ sprite: Clothing/Neck/Medals/clownmedal.rsi state: icon +- type: stealTargetGroup + id: WantedListCartridge + name: steal-target-groups-wanted-list-cartridge + sprite: + sprite: Objects/Devices/cartridge.rsi + state: cart-sec + #Thief structures - type: stealTargetGroup diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index f8e44d831e..7a46d0f5e9 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -184,6 +184,15 @@ - type: Objective difficulty: 1.2 +- type: entity + parent: BaseThiefStealObjective + id: WantedListCartridgeStealObjective + components: + - type: StealCondition + stealGroup: WantedListCartridge + - type: Objective + difficulty: 1 + - type: entity #Medical subgroup parent: BaseThiefStealObjective id: ClothingOuterHardsuitVoidParamedStealObjective diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 4dcef67f77..105ae11048 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -359,6 +359,10 @@ key: KnockedDown time: 0.75 type: Remove + - !type:GenericStatusEffect + key: Adrenaline # Guess what epinephrine is... + component: IgnoreSlowOnDamage + time: 5 # lingers less than hivemind reagent to give it a niche - type: reagent id: Hyronalin diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml index 04b9469046..20e06e9f48 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml @@ -21,3 +21,51 @@ - node: ImprovisedArrow entity: ArrowImprovised + +- type: constructionGraph + id: ImprovisedArrowPlasma + start: start + graph: + - node: start + edges: + - to: ImprovisedArrowPlasma + steps: + - material: MetalRod + amount: 1 + doAfter: 0.5 + - material: Cloth + amount: 1 + doAfter: 0.5 + - tag: PlasmaGlassShard + name: plasma glass shard + icon: + sprite: Objects/Materials/Shards/shard.rsi + state: shard1 + doAfter: 0.5 + + - node: ImprovisedArrowPlasma + entity: ArrowImprovisedPlasma + +- type: constructionGraph + id: ImprovisedArrowUranium + start: start + graph: + - node: start + edges: + - to: ImprovisedArrowUranium + steps: + - material: MetalRod + amount: 1 + doAfter: 0.5 + - material: Cloth + amount: 1 + doAfter: 0.5 + - tag: UraniumGlassShard + name: uranium glass shard + icon: + sprite: Objects/Materials/Shards/shard.rsi + state: shard1 + doAfter: 0.5 + + - node: ImprovisedArrowUranium + entity: ArrowImprovisedUranium diff --git a/Resources/Prototypes/Recipes/Construction/weapons.yml b/Resources/Prototypes/Recipes/Construction/weapons.yml index 040d4c8963..5936a35069 100644 --- a/Resources/Prototypes/Recipes/Construction/weapons.yml +++ b/Resources/Prototypes/Recipes/Construction/weapons.yml @@ -152,6 +152,28 @@ icon: { sprite: Objects/Weapons/Guns/Bow/bow.rsi, state: wielded-arrow } objectType: Item +- type: construction + name: plasma glass shard arrow + id: ImprovisedArrowPlasma + graph: ImprovisedArrowPlasma + startNode: start + targetNode: ImprovisedArrowPlasma + category: construction-category-weapons + description: An arrow tipped with pieces of a plasma glass shard, for use with a bow. + icon: { sprite: Objects/Weapons/Guns/Bow/bow.rsi, state: wielded-arrow } + objectType: Item + +- type: construction + name: uranium glass shard arrow + id: ImprovisedArrowUranium + graph: ImprovisedArrowUranium + startNode: start + targetNode: ImprovisedArrowUranium + category: construction-category-weapons + description: An arrow tipped with pieces of a uranium glass shard, for use with a bow. + icon: { sprite: Objects/Weapons/Guns/Bow/bow.rsi, state: wielded-arrow } + objectType: Item + - type: construction name: improvised bow id: ImprovisedBow diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index f1b847bdb4..9b87495781 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -15,6 +15,9 @@ - type: Tag id: Ambrosia +- type: Tag + id: AnomalyHost + - type: Tag id: AppraisalTool diff --git a/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml b/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml index a2e75f1542..7216871483 100644 --- a/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml +++ b/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml @@ -13,7 +13,7 @@ - Tortila Dough = 15 Cornmeal, 10 Water - Тофу = 5 универсального фермента (Catalyst), 30 соевого молока - Тесто для пирога = 2 яйца (12 ед.), 15 муки, 5 столовой соли -- Тесто для торта = 2 яйца(12 ед.), 15 муки, 5 сахара +- Тесто для торта = 2 яйца(12 ед.), 15 муки, 5 сахара, 5 молока - Веганское тесто для торта = 15 соевого молока, 15 муки, 5 сахара - Масло = 30 молока, 5 столовой соли (Catalyst) - Головка сыра = 5 универсального фермента (Catalyst), 40 молока diff --git a/Resources/Textures/Decals/burnt.rsi/burnt1.png b/Resources/Textures/Decals/burnt.rsi/burnt1.png new file mode 100644 index 0000000000..3fcb7a4949 Binary files /dev/null and b/Resources/Textures/Decals/burnt.rsi/burnt1.png differ diff --git a/Resources/Textures/Decals/burnt.rsi/burnt2.png b/Resources/Textures/Decals/burnt.rsi/burnt2.png new file mode 100644 index 0000000000..01f8f220b2 Binary files /dev/null and b/Resources/Textures/Decals/burnt.rsi/burnt2.png differ diff --git a/Resources/Textures/Decals/burnt.rsi/burnt3.png b/Resources/Textures/Decals/burnt.rsi/burnt3.png new file mode 100644 index 0000000000..e9dcbe3753 Binary files /dev/null and b/Resources/Textures/Decals/burnt.rsi/burnt3.png differ diff --git a/Resources/Textures/Decals/burnt.rsi/burnt4.png b/Resources/Textures/Decals/burnt.rsi/burnt4.png new file mode 100644 index 0000000000..f1db0637cc Binary files /dev/null and b/Resources/Textures/Decals/burnt.rsi/burnt4.png differ diff --git a/Resources/Textures/Decals/burnt.rsi/meta.json b/Resources/Textures/Decals/burnt.rsi/meta.json new file mode 100644 index 0000000000..48a8f33138 --- /dev/null +++ b/Resources/Textures/Decals/burnt.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "From https://github.com/BeeStation/BeeStation-Hornet/blob/master/icons/turf/turf_damage.dmi at f2d6fbdf36aa0951049498cf28e028a38e32fe0b", + "states": [ + { + "name": "burnt1" + + }, + { + "name": "burnt2" + + }, + { + "name": "burnt3" + + }, + { + "name": "burnt4" + + } + ] +} diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png b/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png new file mode 100644 index 0000000000..6a3197004c Binary files /dev/null and b/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png differ diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json index f3b02a2b2e..d5fad56006 100644 --- a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github)", + "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github), cart-sec made by dieselmohawk (discord)", "size": { "x": 32, "y": 32 @@ -79,6 +79,9 @@ { "name": "cart-y" }, + { + "name": "cart-sec" + }, { "name": "insert_overlay" } diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png new file mode 100644 index 0000000000..5e9311e064 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png new file mode 100644 index 0000000000..b594cac2a9 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/bluespace_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png new file mode 100644 index 0000000000..a6fcde7a18 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png new file mode 100644 index 0000000000..91eb395704 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/fire_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png new file mode 100644 index 0000000000..6e178307c3 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png new file mode 100644 index 0000000000..b1a1c43da0 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flesh_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png new file mode 100644 index 0000000000..70e369cb02 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png new file mode 100644 index 0000000000..97969116a9 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/flora_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png new file mode 100644 index 0000000000..4823cb5a6f Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png new file mode 100644 index 0000000000..346cad09b1 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/frost_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png new file mode 100644 index 0000000000..c0775c0fb9 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png new file mode 100644 index 0000000000..04bfb8a0bf Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/grav_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json new file mode 100644 index 0000000000..4e069d0cd9 --- /dev/null +++ b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json @@ -0,0 +1,643 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by TheShuEd (github) for Space Station 14", + "states": [ + { + "name": "bluespace", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "bluespace_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "fire", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "fire_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flesh", + "directions": 4, + "delays": [ + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flesh_VOX", + "directions": 4, + "delays": [ + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ], + [ + 0.8, + 0.2, + 0.2 + ] + ] + }, + { + "name": "flora", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "flora_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "frost", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "frost_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "grav", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "grav_VOX", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "rock", + "directions": 4, + "delays": [ + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "rock_VOX", + "directions": 4, + "delays": [ + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ], + [ + 0.9, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shadow", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shadow_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shock", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "shock_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "tech", + "directions": 4, + "delays": [ + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ] + ] + }, + { + "name": "tech_VOX", + "directions": 4, + "delays": [ + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ], + [ + 0.2, + 0.4, + 0.2, + 0.4 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png new file mode 100644 index 0000000000..770e4b8d2f Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png new file mode 100644 index 0000000000..b11a9182fb Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/rock_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png new file mode 100644 index 0000000000..345248bc53 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png new file mode 100644 index 0000000000..6efb246ac1 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shadow_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png new file mode 100644 index 0000000000..acdc87a291 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png new file mode 100644 index 0000000000..d1f011c44d Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/shock_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png new file mode 100644 index 0000000000..0611fa8a2a Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png new file mode 100644 index 0000000000..1787cdd4e0 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/tech_VOX.png differ diff --git a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png index c0e547f8dd..d8b6f14c11 100644 Binary files a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png and b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5-pulse.png differ diff --git a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png index 6117776649..a36dd2b64a 100644 Binary files a/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png and b/Resources/Textures/Structures/Specific/anomaly.rsi/anom5.png differ diff --git a/RobustToolbox b/RobustToolbox index 0f60ad9018..c86cb0b795 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 0f60ad9018f54f9b49da1810bbffa01e2c5975f7 +Subproject commit c86cb0b7951cc4a662c7292138c5f45d868c5f58 diff --git a/Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt new file mode 100644 index 0000000000..083e803ecd --- /dev/null +++ b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate README.txt @@ -0,0 +1,4 @@ +This is a template that greatly simplifies working with IconSmooth sprites. + +A video on how to use it can be found at the link: +https://github.com/space-wizards/space-station-14/pull/32210 \ No newline at end of file diff --git a/Tools/SS14 Aseprite Templates/IconSmoothingTemplate.aseprite b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate.aseprite new file mode 100644 index 0000000000..112a1d932c Binary files /dev/null and b/Tools/SS14 Aseprite Templates/IconSmoothingTemplate.aseprite differ diff --git a/Tools/publish_multi_request.py b/Tools/publish_multi_request.py new file mode 100755 index 0000000000..a63359afd6 --- /dev/null +++ b/Tools/publish_multi_request.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +import requests +import os +import subprocess +from typing import Iterable + +PUBLISH_TOKEN = os.environ["PUBLISH_TOKEN"] +VERSION = os.environ["GITHUB_SHA"] + +RELEASE_DIR = "release" + +# +# CONFIGURATION PARAMETERS +# Forks should change these to publish to their own infrastructure. +# +ROBUST_CDN_URL = "https://wizards.cdn.spacestation14.com/" +FORK_ID = "wizards" + +def main(): + session = requests.Session() + session.headers = { + "Authorization": f"Bearer {PUBLISH_TOKEN}", + } + + print(f"Starting publish on Robust.Cdn for version {VERSION}") + + data = { + "version": VERSION, + "engineVersion": get_engine_version(), + } + headers = { + "Content-Type": "application/json" + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/start", json=data, headers=headers) + resp.raise_for_status() + print("Publish successfully started, adding files...") + + for file in get_files_to_publish(): + print(f"Publishing {file}") + with open(file, "rb") as f: + headers = { + "Content-Type": "application/octet-stream", + "Robust-Cdn-Publish-File": os.path.basename(file), + "Robust-Cdn-Publish-Version": VERSION + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/file", data=f, headers=headers) + + resp.raise_for_status() + + print("Successfully pushed files, finishing publish...") + + data = { + "version": VERSION + } + headers = { + "Content-Type": "application/json" + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/finish", json=data, headers=headers) + resp.raise_for_status() + + print("SUCCESS!") + + +def get_files_to_publish() -> Iterable[str]: + for file in os.listdir(RELEASE_DIR): + yield os.path.join(RELEASE_DIR, file) + + +def get_engine_version() -> str: + proc = subprocess.run(["git", "describe","--tags", "--abbrev=0"], stdout=subprocess.PIPE, cwd="RobustToolbox", check=True, encoding="UTF-8") + tag = proc.stdout.strip() + assert tag.startswith("v") + return tag[1:] # Cut off v prefix. + + +if __name__ == '__main__': + main()