Merge remote-tracking branch 'refs/remotes/wizards/master'
# Conflicts: # Content.Shared/StatusEffect/StatusEffectsSystem.cs # Resources/Audio/Effects/attributions.yml # Resources/Locale/en-US/_strings/anomaly/inner_anomaly.ftl # Resources/Prototypes/Catalog/Fills/Lockers/heads.yml # Resources/Prototypes/Entities/Mobs/Species/base.yml # Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml # Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml
This commit is contained in:
commit
47cdabdaed
162 changed files with 3518 additions and 459 deletions
18
.github/workflows/publish.yml
vendored
18
.github/workflows/publish.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ namespace Content.Client.Actions.UI
|
|||
{
|
||||
var duration = Cooldown.Value.End - Cooldown.Value.Start;
|
||||
|
||||
if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
|
||||
if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup))
|
||||
return;
|
||||
|
||||
_cooldownLabel.SetMessage(markup);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration.UI.SetOutfit
|
||||
|
|
@ -65,9 +64,18 @@ namespace Content.Client.Administration.UI.SetOutfit
|
|||
PopulateByFilter(SearchBar.Text);
|
||||
}
|
||||
|
||||
private IEnumerable<StartingGearPrototype> GetPrototypes()
|
||||
{
|
||||
// Filter out any StartingGearPrototypes that belong to loadouts
|
||||
var loadouts = _prototypeManager.EnumeratePrototypes<LoadoutPrototype>();
|
||||
var loadoutGears = loadouts.Select(l => l.StartingGear);
|
||||
return _prototypeManager.EnumeratePrototypes<StartingGearPrototype>()
|
||||
.Where(p => !loadoutGears.Contains(p.ID));
|
||||
}
|
||||
|
||||
private void PopulateList()
|
||||
{
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
foreach (var gear in GetPrototypes())
|
||||
{
|
||||
OutfitList.Add(GetItem(gear, OutfitList));
|
||||
}
|
||||
|
|
@ -76,7 +84,7 @@ namespace Content.Client.Administration.UI.SetOutfit
|
|||
private void PopulateByFilter(string filter)
|
||||
{
|
||||
OutfitList.Clear();
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
foreach (var gear in GetPrototypes())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filter) &&
|
||||
gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
|||
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete);
|
||||
}
|
||||
|
||||
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
|
||||
{
|
||||
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
|
||||
|
|
@ -75,4 +76,13 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<AnomalySupercriticalComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.Scale = Vector2.One;
|
||||
sprite.Color = sprite.Color.WithAlpha(1f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs
Normal file
50
Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Anomaly.Effects;
|
||||
|
||||
public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
|
||||
}
|
||||
|
||||
private void OnAfterHandleState(Entity<InnerBodyAnomalyComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
if (ent.Comp.FallbackSprite is null)
|
||||
return;
|
||||
|
||||
if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index))
|
||||
index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap);
|
||||
|
||||
if (TryComp<BodyComponent>(ent, out var body) &&
|
||||
body.Prototype is not null &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite))
|
||||
{
|
||||
sprite.LayerSetSprite(index, speciesSprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetSprite(index, ent.Comp.FallbackSprite);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(index, true);
|
||||
sprite.LayerSetShader(index, "unshaded");
|
||||
}
|
||||
|
||||
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
var index = sprite.LayerMapGet(ent.Comp.LayerMap);
|
||||
sprite.LayerSetVisible(index, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -306,6 +306,9 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
|||
.WithMaxDistance(comp.Range);
|
||||
|
||||
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
|
||||
playingCount++;
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
|||
if(!_adminAudioEnabled) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_adminAudio.Add(stream.Value.Entity);
|
||||
_adminAudio.Add(stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
|
||||
|
|
@ -76,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
|||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_eventAudio.Add(soundEvent.Type, stream.Value.Entity);
|
||||
_eventAudio.Add(soundEvent.Type, stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
|
||||
|
|
|
|||
|
|
@ -213,9 +213,9 @@ public sealed partial class ContentAudioSystem
|
|||
false,
|
||||
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
|
||||
|
||||
_ambientMusicStream = strim.Value.Entity;
|
||||
_ambientMusicStream = strim?.Entity;
|
||||
|
||||
if (_musicProto.FadeIn)
|
||||
if (_musicProto.FadeIn && strim != null)
|
||||
{
|
||||
FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ public sealed partial class ContentAudioSystem
|
|||
false,
|
||||
_lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
|
||||
);
|
||||
if (playResult.Value.Entity == default)
|
||||
if (playResult == null)
|
||||
{
|
||||
_sawmill.Warning(
|
||||
$"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!",
|
||||
|
|
|
|||
30
Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
Normal file
30
Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WantedRecord> _wantedRecords = new();
|
||||
|
||||
public WantedListUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
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<WantedRecord> 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<ListData>());
|
||||
|
||||
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<SecurityIconPrototype>(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;
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<cartridges:WantedListUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'wanted-list-search-placeholder'}"/>
|
||||
|
||||
<BoxContainer Name="MainContainer" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Label Name="NoRecords" Text="{Loc 'wanted-list-label-no-records'}" Align="Center" VAlign="Center" HorizontalExpand="True" FontColorOverride="DarkGray"/>
|
||||
|
||||
<!-- Any attempts to set dimensions for ListContainer breaks the renderer, I have to roughly set sizes and margins in other controllers. -->
|
||||
<controls:ListContainer
|
||||
Name="RecordsList"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalExpand="True"
|
||||
Visible="False"
|
||||
Toggle="True"
|
||||
Group="True"
|
||||
SetWidth="192" />
|
||||
|
||||
<Label Name="RecordUnselected"
|
||||
Text="{Loc 'criminal-records-console-select-record-info'}"
|
||||
Align="Center"
|
||||
FontColorOverride="DarkGray"
|
||||
Visible="False"
|
||||
HorizontalExpand="True" />
|
||||
<BoxContainer Name="PersonContainer" Orientation="Vertical" HorizontalExpand="True" SetWidth="334" Margin="5 0 77 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="PersonName" StyleClasses="LabelBig" />
|
||||
<RichTextLabel Name="PersonState" HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Name="DataContainer" Orientation="Vertical">
|
||||
<RichTextLabel Name="TargetAge" />
|
||||
<RichTextLabel Name="TargetJob" />
|
||||
<RichTextLabel Name="TargetSpecies" />
|
||||
<RichTextLabel Name="TargetGender" />
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
|
||||
<RichTextLabel Name="InitiatorName" VerticalAlignment="Stretch"/>
|
||||
<RichTextLabel Name="WantedReason" VerticalAlignment="Stretch"/>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
<controls:TableContainer Name="HistoryTable" Columns="3" Visible="False" HorizontalAlignment="Stretch" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</cartridges:WantedListUiFragment>
|
||||
|
|
@ -2,7 +2,4 @@ using Content.Shared.Explosion.EntitySystems;
|
|||
|
||||
namespace Content.Client.Explosion.EntitySystems;
|
||||
|
||||
public sealed class ExplosionSystem : SharedExplosionSystem
|
||||
{
|
||||
|
||||
}
|
||||
public sealed class ExplosionSystem : SharedExplosionSystem;
|
||||
|
|
|
|||
|
|
@ -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<ShaderPrototype>("FlashedEffect").InstanceUnique();
|
||||
_flash = _entityManager.System<SharedFlashSystem>();
|
||||
_statusSys = _entityManager.System<StatusEffectsSystem>();
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ namespace Content.Client.Flash
|
|||
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(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;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
ToolTip="Pick (Hold 5)" />
|
||||
<mapping:MappingActionsButton Name="Delete" Access="Public"
|
||||
ToolTip="Delete (Hold 6)" />
|
||||
<mapping:MappingActionsButton Name="Flip" Access="Public" ToggleMode="False"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</LayoutContainer>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
|
||||
PlaceHolder="{Loc crew-monitor-filter-line-placeholder}" />
|
||||
|
||||
<ScrollContainer Name="SensorScroller"
|
||||
VerticalExpand="True"
|
||||
SetWidth="520"
|
||||
|
|
|
|||
|
|
@ -156,6 +156,11 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||
// Populate departments
|
||||
foreach (var sensor in departmentSensors)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SearchLineEdit.Text)
|
||||
&& !sensor.Name.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase)
|
||||
&& !sensor.Job.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||
|
||||
// Add a button that will hold a username and other details
|
||||
|
|
|
|||
|
|
@ -199,7 +199,9 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
|
||||
var gridMatrix = _transform.GetWorldMatrix(gUid);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
|
||||
var color = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
|
||||
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f);
|
||||
|
||||
// Others default:
|
||||
// Color.FromHex("#FFC000FF")
|
||||
|
|
@ -213,25 +215,52 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
|
||||
var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
|
||||
gridCentre.Y = -gridCentre.Y;
|
||||
|
||||
var distance = gridCentre.Length();
|
||||
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
|
||||
("distance", $"{distance:0.0}"));
|
||||
|
||||
var mapCoords = _transform.GetWorldPosition(gUid);
|
||||
var coordsText = $"({mapCoords.X:0.0}, {mapCoords.Y:0.0})";
|
||||
|
||||
// yes 1.0 scale is intended here.
|
||||
var labelDimensions = handle.GetDimensions(Font, labelText, 1f);
|
||||
var coordsDimensions = handle.GetDimensions(Font, coordsText, 0.7f);
|
||||
|
||||
// y-offset the control to always render below the grid (vertically)
|
||||
var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f;
|
||||
|
||||
// The actual position in the UI. We offset the matrix position to render it off by half its width
|
||||
// plus by the offset.
|
||||
var uiPosition = ScalePosition(gridCentre)- new Vector2(labelDimensions.X / 2f, -yOffset);
|
||||
// The actual position in the UI. We centre the label by offsetting the matrix position
|
||||
// by half the label's width, plus the y-offset
|
||||
var gridScaledPosition = ScalePosition(gridCentre) - new Vector2(0, -yOffset);
|
||||
|
||||
// Look this is uggo so feel free to cleanup. We just need to clamp the UI position to within the viewport.
|
||||
uiPosition = new Vector2(Math.Clamp(uiPosition.X, 0f, PixelWidth - labelDimensions.X ),
|
||||
Math.Clamp(uiPosition.Y, 0f, PixelHeight - labelDimensions.Y));
|
||||
// Normalize the grid position if it exceeds the viewport bounds
|
||||
// normalizing it instead of clamping it preserves the direction of the vector and prevents corner-hugging
|
||||
var gridOffset = gridScaledPosition / PixelSize - new Vector2(0.5f, 0.5f);
|
||||
var offsetMax = Math.Max(Math.Abs(gridOffset.X), Math.Abs(gridOffset.Y)) * 2f;
|
||||
if (offsetMax > 1)
|
||||
{
|
||||
gridOffset = new Vector2(gridOffset.X / offsetMax, gridOffset.Y / offsetMax);
|
||||
|
||||
handle.DrawString(Font, uiPosition, labelText, color);
|
||||
gridScaledPosition = (gridOffset + new Vector2(0.5f, 0.5f)) * PixelSize;
|
||||
}
|
||||
|
||||
var labelUiPosition = gridScaledPosition - new Vector2(labelDimensions.X / 2f, 0);
|
||||
var coordUiPosition = gridScaledPosition - new Vector2(coordsDimensions.X / 2f, -labelDimensions.Y);
|
||||
|
||||
// clamp the IFF label's UI position to within the viewport extents so it hugs the edges of the viewport
|
||||
// coord label intentionally isn't clamped so we don't get ugly clutter at the edges
|
||||
var controlExtents = PixelSize - new Vector2(labelDimensions.X, labelDimensions.Y); //new Vector2(labelDimensions.X * 2f, labelDimensions.Y);
|
||||
labelUiPosition = Vector2.Clamp(labelUiPosition, Vector2.Zero, controlExtents);
|
||||
|
||||
// draw IFF label
|
||||
handle.DrawString(Font, labelUiPosition, labelText, labelColor);
|
||||
|
||||
// only draw coords label if close enough
|
||||
if (offsetMax < 1)
|
||||
{
|
||||
handle.DrawString(Font, coordUiPosition, coordsText, 0.7f, coordColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Detailed view
|
||||
|
|
@ -241,7 +270,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
if (!gridAABB.Intersects(viewAABB))
|
||||
continue;
|
||||
|
||||
DrawGrid(handle, matty, grid, color);
|
||||
DrawGrid(handle, matty, grid, labelColor);
|
||||
DrawDocks(handle, gUid, matty);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PanelContainer>().Class(ContextMenuPopup.StyleClassContextMenuPopup)
|
||||
.Prop(PanelContainer.StylePropertyPanel, contextMenuBackground),
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public sealed class ParacusiaSystem : SharedParacusiaSystem
|
|||
var newCoords = Transform(uid).Coordinates.Offset(randomOffset);
|
||||
|
||||
// Play the sound
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords).Value.Entity;
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords)?.Entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,10 +47,11 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
|||
if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null)
|
||||
return;
|
||||
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true).Value.Entity;
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
|
||||
|
||||
if (!TryComp(weather.Stream, out AudioComponent? comp))
|
||||
return;
|
||||
|
||||
var stream = weather.Stream.Value;
|
||||
var comp = Comp<AudioComponent>(stream);
|
||||
var occlusion = 0f;
|
||||
|
||||
// Work out tiles nearby to determine volume.
|
||||
|
|
@ -115,7 +116,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
|||
|
||||
var alpha = GetPercent(weather, uid);
|
||||
alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume);
|
||||
_audio.SetGain(stream, alpha, comp);
|
||||
_audio.SetGain(weather.Stream, alpha, comp);
|
||||
comp.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -584,17 +584,10 @@ namespace Content.Client.Wires.UI
|
|||
|
||||
private sealed class HelpPopup : Popup
|
||||
{
|
||||
private const string Text = "Click on the gold contacts with a multitool in hand to pulse their wire.\n" +
|
||||
"Click on the wires with a pair of wirecutters in hand to cut/mend them.\n\n" +
|
||||
"The lights at the top show the state of the machine, " +
|
||||
"messing with wires will probably do stuff to them.\n" +
|
||||
"Wire layouts are different each round, " +
|
||||
"but consistent between machines of the same type.";
|
||||
|
||||
public HelpPopup()
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(Text);
|
||||
label.SetMessage(Loc.GetString("wires-menu-help-popup"));
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
StyleClasses = {ExamineSystem.StyleClassEntityTooltip},
|
||||
|
|
|
|||
108
Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs
Normal file
108
Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs
Normal file
|
|
@ -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<IServerEntityManager>();
|
||||
var buckleSystem = entMan.System<SharedBuckleSystem>();
|
||||
|
||||
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<IServerEntityManager>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -4,11 +4,15 @@ using Content.Server.Hands.Systems;
|
|||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
@ -82,9 +86,11 @@ namespace Content.Server.Administration.Commands
|
|||
return false;
|
||||
|
||||
HumanoidCharacterProfile? profile = null;
|
||||
ICommonSession? session = null;
|
||||
// Check if we are setting the outfit of a player to respect the preferences
|
||||
if (entityManager.TryGetComponent(target, out ActorComponent? actorComponent))
|
||||
{
|
||||
session = actorComponent.PlayerSession;
|
||||
var userId = actorComponent.PlayerSession.UserId;
|
||||
var preferencesManager = IoCManager.Resolve<IServerPreferencesManager>();
|
||||
var prefs = preferencesManager.GetPreferences(userId);
|
||||
|
|
@ -128,6 +134,36 @@ namespace Content.Server.Administration.Commands
|
|||
}
|
||||
}
|
||||
|
||||
// See if this starting gear is associated with a job
|
||||
var jobs = prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (job.StartingGear != gear)
|
||||
continue;
|
||||
|
||||
var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID);
|
||||
if (!prototypeManager.TryIndex<RoleLoadoutPrototype>(jobProtoId, out var jobProto))
|
||||
break;
|
||||
|
||||
// Don't require a player, so this works on Urists
|
||||
profile ??= entityManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var comp)
|
||||
? HumanoidCharacterProfile.DefaultWithSpecies(comp.Species)
|
||||
: new HumanoidCharacterProfile();
|
||||
// Try to get the user's existing loadout for the role
|
||||
profile.Loadouts.TryGetValue(jobProtoId, out var roleLoadout);
|
||||
|
||||
if (roleLoadout == null)
|
||||
{
|
||||
// If they don't have a loadout for the role, make a default one
|
||||
roleLoadout = new RoleLoadout(jobProtoId);
|
||||
roleLoadout.SetDefault(profile, session, prototypeManager);
|
||||
}
|
||||
|
||||
// Equip the target with the job loadout
|
||||
var stationSpawning = entityManager.System<SharedStationSpawningSystem>();
|
||||
stationSpawning.EquipRoleLoadout(target, roleLoadout, jobProto);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
|
|
@ -10,6 +11,7 @@ using Content.Shared.Popups;
|
|||
using Content.Shared.Power;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
|
|
@ -25,6 +27,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -40,6 +43,34 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var sync, out var xform))
|
||||
{
|
||||
if (sync.ConnectedAnomaly is null)
|
||||
continue;
|
||||
|
||||
if (_timing.CurTime < sync.NextCheckTime)
|
||||
continue;
|
||||
sync.NextCheckTime += sync.CheckFrequency;
|
||||
|
||||
if (Transform(sync.ConnectedAnomaly.Value).MapUid != Transform(uid).MapUid)
|
||||
{
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!xform.Coordinates.TryDistance(EntityManager, Transform(sync.ConnectedAnomaly.Value).Coordinates, out var distance))
|
||||
continue;
|
||||
|
||||
if (distance > sync.AttachRange)
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If powered, try to attach a nearby anomaly.
|
||||
/// </summary>
|
||||
|
|
@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnomalyComponent>(ent.Comp.ConnectedAnomaly, out var anomaly))
|
||||
if (ent.Comp.ConnectedAnomaly is null)
|
||||
return;
|
||||
|
||||
DisconnectFromAnomaly(ent, anomaly);
|
||||
DisconnectFromAnomaly(ent, ent.Comp.ConnectedAnomaly.Value);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
|
||||
|
|
@ -125,13 +156,16 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
|||
|
||||
//TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid other)
|
||||
{
|
||||
if (ent.Comp.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
if (TryComp<AnomalyComponent>(other, out var anomaly))
|
||||
{
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large);
|
||||
_audio.PlayPvs(ent.Comp.ConnectedSound, ent);
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||
SubscribeLocalEvent<AnomalyComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<AnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||
|
||||
|
||||
InitializeGenerator();
|
||||
InitializeScanner();
|
||||
InitializeVessel();
|
||||
|
|
@ -86,7 +87,10 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||
|
||||
private void OnShutdown(Entity<AnomalyComponent> anomaly, ref ComponentShutdown args)
|
||||
{
|
||||
EndAnomaly(anomaly);
|
||||
if (anomaly.Comp.CurrentBehavior is not null)
|
||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||
|
||||
EndAnomaly(anomaly, spawnCore: false);
|
||||
}
|
||||
|
||||
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Content.Server.Anomaly.Components;
|
|||
/// <summary>
|
||||
/// a device that allows you to translate anomaly activity into multitool signals.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))]
|
||||
[RegisterComponent, AutoGenerateComponentPause, Access(typeof(AnomalySynchronizerSystem))]
|
||||
public sealed partial class AnomalySynchronizerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -34,6 +34,15 @@ public sealed partial class AnomalySynchronizerComponent : Component
|
|||
[DataField]
|
||||
public float AttachRange = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Periodicheski checks to see if the anomaly has moved to disconnect it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan CheckFrequency = TimeSpan.FromSeconds(1f);
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextCheckTime = TimeSpan.Zero;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> DecayingPort = "Decaying";
|
||||
|
||||
|
|
|
|||
236
Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs
Normal file
236
Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
|
||||
private readonly Color _messageColor = Color.FromSrgb(new Color(201, 22, 94));
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyInjectorComponent, StartCollideEvent>(OnStartCollideInjector);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalyPulseEvent>(OnAnomalyPulse);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalyShutdownEvent>(OnAnomalyShutdown);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalySupercriticalEvent>(OnAnomalySupercritical);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AnomalySeverityChangedEvent>(OnSeverityChanged);
|
||||
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
SubscribeLocalEvent<AnomalyComponent, ActionAnomalyPulseEvent>(OnActionPulse);
|
||||
}
|
||||
|
||||
private void OnActionPulse(Entity<AnomalyComponent> ent, ref ActionAnomalyPulseEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(ent, ent.Comp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnStartCollideInjector(Entity<InnerBodyAnomalyInjectorComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (ent.Comp.Whitelist is not null && !_whitelist.IsValid(ent.Comp.Whitelist, args.OtherEntity))
|
||||
return;
|
||||
if (TryComp<InnerBodyAnomalyComponent>(args.OtherEntity, out var innerAnom) && innerAnom.Injected)
|
||||
return;
|
||||
if (!_mind.TryGetMind(args.OtherEntity, out _, out var mindComponent))
|
||||
return;
|
||||
|
||||
EntityManager.AddComponents(args.OtherEntity, ent.Comp.InjectionComponents);
|
||||
QueueDel(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<InnerBodyAnomalyComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
AddAnomalyToBody(ent);
|
||||
}
|
||||
|
||||
private void AddAnomalyToBody(Entity<InnerBodyAnomalyComponent> ent)
|
||||
{
|
||||
if (!_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
return;
|
||||
|
||||
if (ent.Comp.Injected)
|
||||
return;
|
||||
|
||||
ent.Comp.Injected = true;
|
||||
|
||||
EntityManager.AddComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.StartSound is not null)
|
||||
_audio.PlayPvs(ent.Comp.StartSound, ent);
|
||||
|
||||
if (ent.Comp.StartMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.StartMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly,LogImpact.Extreme,$"{ToPrettyString(ent)} became anomaly host.");
|
||||
}
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnAnomalyPulse(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyPulseEvent args)
|
||||
{
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
_jitter.DoJitter(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration / 2 * args.Severity), true);
|
||||
}
|
||||
|
||||
private void OnAnomalySupercritical(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!TryComp<BodyComponent>(ent, out var body))
|
||||
return;
|
||||
|
||||
_body.GibBody(ent, true, body, splatModifier: 5f);
|
||||
}
|
||||
|
||||
private void OnSeverityChanged(Entity<InnerBodyAnomalyComponent> ent, ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
if (!_mind.TryGetMind(ent, out _, out var mindComponent) || mindComponent.Session == null)
|
||||
return;
|
||||
|
||||
var message = string.Empty;
|
||||
|
||||
if (args.Severity >= 0.5 && ent.Comp.LastSeverityInformed < 0.5)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.5f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-50");
|
||||
}
|
||||
if (args.Severity >= 0.75 && ent.Comp.LastSeverityInformed < 0.75)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.75f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-75");
|
||||
}
|
||||
if (args.Severity >= 0.9 && ent.Comp.LastSeverityInformed < 0.9)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 0.9f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-90");
|
||||
}
|
||||
if (args.Severity >= 1 && ent.Comp.LastSeverityInformed < 1)
|
||||
{
|
||||
ent.Comp.LastSeverityInformed = 1f;
|
||||
message = Loc.GetString("inner-anomaly-severity-info-100");
|
||||
}
|
||||
|
||||
if (message == string.Empty)
|
||||
return;
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<InnerBodyAnomalyComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead)
|
||||
return;
|
||||
|
||||
_anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it
|
||||
}
|
||||
|
||||
private void OnAnomalyShutdown(Entity<InnerBodyAnomalyComponent> ent, ref AnomalyShutdownEvent args)
|
||||
{
|
||||
RemoveAnomalyFromBody(ent);
|
||||
RemCompDeferred<InnerBodyAnomalyComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
RemoveAnomalyFromBody(ent);
|
||||
}
|
||||
|
||||
private void RemoveAnomalyFromBody(Entity<InnerBodyAnomalyComponent> ent)
|
||||
{
|
||||
if (!ent.Comp.Injected)
|
||||
return;
|
||||
|
||||
if (_proto.TryIndex(ent.Comp.InjectionProto, out var injectedAnom))
|
||||
EntityManager.RemoveComponents(ent, injectedAnom.Components);
|
||||
|
||||
_stun.TryParalyze(ent, TimeSpan.FromSeconds(ent.Comp.StunDuration), true);
|
||||
|
||||
if (ent.Comp.EndMessage is not null &&
|
||||
_mind.TryGetMind(ent, out _, out var mindComponent) &&
|
||||
mindComponent.Session != null)
|
||||
{
|
||||
var message = Loc.GetString(ent.Comp.EndMessage);
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chat.ChatMessageToOne(ChatChannel.Server,
|
||||
message,
|
||||
wrappedMessage,
|
||||
default,
|
||||
false,
|
||||
mindComponent.Session.Channel,
|
||||
_messageColor);
|
||||
|
||||
|
||||
_popup.PopupEntity(message, ent, ent, PopupType.MediumCaution);
|
||||
|
||||
_adminLog.Add(LogType.Anomaly, LogImpact.Medium,$"{ToPrettyString(ent)} is no longer a host for the anomaly.");
|
||||
}
|
||||
|
||||
ent.Comp.Injected = false;
|
||||
RemCompDeferred<AnomalyComponent>(ent);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public sealed class TechAnomalySystem : EntitySystem
|
|||
if (_timing.CurTime < tech.NextTimer)
|
||||
continue;
|
||||
|
||||
tech.NextTimer += TimeSpan.FromSeconds(tech.TimerFrequency * anom.Stability);
|
||||
tech.NextTimer += TimeSpan.FromSeconds(tech.TimerFrequency);
|
||||
|
||||
_signal.InvokePort(uid, tech.TimerPort);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Server.Decals;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
[Dependency] private readonly DecalSystem _decalSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private const int HotspotSoundCooldownCycles = 200;
|
||||
|
||||
private int _hotspotSoundCooldown = 0;
|
||||
|
|
@ -56,7 +58,30 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
if (tile.Hotspot.Bypassing)
|
||||
{
|
||||
tile.Hotspot.State = 3;
|
||||
// TODO ATMOS: Burn tile here
|
||||
|
||||
var gridUid = ent.Owner;
|
||||
var tilePos = tile.GridIndices;
|
||||
|
||||
// Get the existing decals on the tile
|
||||
var tileDecals = _decalSystem.GetDecalsInRange(gridUid, tilePos);
|
||||
|
||||
// Count the burnt decals on the tile
|
||||
var tileBurntDecals = 0;
|
||||
|
||||
foreach (var set in tileDecals)
|
||||
{
|
||||
if (Array.IndexOf(_burntDecals, set.Decal.Id) == -1)
|
||||
continue;
|
||||
|
||||
tileBurntDecals++;
|
||||
|
||||
if (tileBurntDecals > 4)
|
||||
break;
|
||||
}
|
||||
|
||||
// Add a random burned decal to the tile only if there are less than 4 of them
|
||||
if (tileBurntDecals < 4)
|
||||
_decalSystem.TryAddDecal(_burntDecals[_random.Next(_burntDecals.Length)], new EntityCoordinates(gridUid, tilePos), out _, cleanable: true);
|
||||
|
||||
if (tile.Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Server.Body.Systems;
|
|||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
|
|
@ -12,7 +13,9 @@ using Robust.Shared.Audio.Systems;
|
|||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
|
|
@ -36,6 +39,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] public readonly PuddleSystem Puddle = default!;
|
||||
|
||||
private const float ExposedUpdateDelay = 1f;
|
||||
|
|
@ -47,6 +51,8 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
private EntityQuery<FirelockComponent> _firelockQuery;
|
||||
private HashSet<EntityUid> _entSet = new();
|
||||
|
||||
private string[] _burntDecals = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -66,7 +72,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
_firelockQuery = GetEntityQuery<FirelockComponent>();
|
||||
|
||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
|
||||
CacheDecals();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
|
@ -81,6 +89,12 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
InvalidateTile(ev.NewTile.GridUid, ev.NewTile.GridIndices);
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev)
|
||||
{
|
||||
if (ev.WasModified<DecalPrototype>())
|
||||
CacheDecals();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
|
@ -107,4 +121,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
|
||||
_exposedTimer -= ExposedUpdateDelay;
|
||||
}
|
||||
|
||||
private void CacheDecals()
|
||||
{
|
||||
_burntDecals = _prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,12 +291,13 @@ public partial class SeedData
|
|||
CanScream = CanScream,
|
||||
TurnIntoKudzu = TurnIntoKudzu,
|
||||
SplatPrototype = SplatPrototype,
|
||||
Mutations = Mutations,
|
||||
Mutations = new List<RandomPlantMutation>(),
|
||||
|
||||
// Newly cloned seed is unique. No need to unnecessarily clone if repeatedly modified.
|
||||
Unique = true,
|
||||
};
|
||||
|
||||
newSeed.Mutations.AddRange(Mutations);
|
||||
return newSeed;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Shared.Security;
|
||||
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class WantedListCartridgeComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -16,31 +16,31 @@ public sealed partial class SolutionRegenerationComponent : Component
|
|||
/// <summary>
|
||||
/// The name of the solution to add to.
|
||||
/// </summary>
|
||||
[DataField("solution", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution", required: true)]
|
||||
public string SolutionName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The solution to add reagents to.
|
||||
/// </summary>
|
||||
[DataField("solutionRef")]
|
||||
public Entity<SolutionComponent>? Solution = null;
|
||||
[DataField]
|
||||
public Entity<SolutionComponent>? SolutionRef = null;
|
||||
|
||||
/// <summary>
|
||||
/// The reagent(s) to be regenerated in the solution.
|
||||
/// </summary>
|
||||
[DataField("generated", required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(required: true)]
|
||||
public Solution Generated = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to regenerate once.
|
||||
/// </summary>
|
||||
[DataField("duration"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan Duration = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// The time when the next regeneration will occur.
|
||||
/// </summary>
|
||||
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextRegenTime = TimeSpan.FromSeconds(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem
|
|||
|
||||
// timer ignores if its full, it's just a fixed cycle
|
||||
regen.NextRegenTime = _timing.CurTime + regen.Duration;
|
||||
if (_solutionContainer.ResolveSolution((uid, manager), regen.SolutionName, ref regen.Solution, out var solution))
|
||||
if (_solutionContainer.ResolveSolution((uid, manager), regen.SolutionName, ref regen.SolutionRef, out var solution))
|
||||
{
|
||||
var amount = FixedPoint2.Min(solution.AvailableVolume, regen.Generated.Volume);
|
||||
if (amount <= FixedPoint2.Zero)
|
||||
|
|
@ -41,7 +41,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem
|
|||
generated = regen.Generated.Clone().SplitSolution(amount);
|
||||
}
|
||||
|
||||
_solutionContainer.TryAddSolution(regen.Solution.Value, generated);
|
||||
_solutionContainer.TryAddSolution(regen.SolutionRef.Value, generated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,50 @@
|
|||
using Content.Server.Stack;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Construction.Completions
|
||||
namespace Content.Server.Construction.Completions;
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class GivePrototype : IGraphAction
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class GivePrototype : IGraphAction
|
||||
{
|
||||
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Prototype { get; private set; } = string.Empty;
|
||||
[DataField("amount")]
|
||||
public int Amount { get; private set; } = 1;
|
||||
[DataField]
|
||||
public EntProtoId Prototype { get; private set; } = string.Empty;
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
[DataField]
|
||||
public int Amount { get; private set; } = 1;
|
||||
|
||||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Prototype))
|
||||
return;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Prototype))
|
||||
var stackSystem = entityManager.EntitySysManager.GetEntitySystem<StackSystem>();
|
||||
var stacks = stackSystem.SpawnMultiple(Prototype, Amount, userUid ?? uid);
|
||||
|
||||
if (userUid is null || !entityManager.TryGetComponent(userUid, out HandsComponent? handsComp))
|
||||
return;
|
||||
|
||||
var coordinates = entityManager.GetComponent<TransformComponent>(userUid ?? uid).Coordinates;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(Prototype))
|
||||
foreach (var item in stacks)
|
||||
{
|
||||
var stackEnt = entityManager.SpawnEntity(Prototype, coordinates);
|
||||
var stack = entityManager.GetComponent<StackComponent>(stackEnt);
|
||||
entityManager.EntitySysManager.GetEntitySystem<StackSystem>().SetCount(stackEnt, Amount, stack);
|
||||
entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>().PickupOrDrop(userUid, stackEnt);
|
||||
stackSystem.TryMergeToHands(item, userUid.Value, hands: handsComp);
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
var handsSystem = entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>();
|
||||
var handsComp = userUid is not null ? entityManager.GetComponent<HandsComponent>(userUid.Value) : null;
|
||||
for (var i = 0; i < Amount; i++)
|
||||
{
|
||||
for (var i = 0; i < Amount; i++)
|
||||
{
|
||||
var item = entityManager.SpawnEntity(Prototype, coordinates);
|
||||
entityManager.EntitySysManager.GetEntitySystem<SharedHandsSystem>().PickupOrDrop(userUid, item);
|
||||
}
|
||||
var item = entityManager.SpawnNextToOrDrop(Prototype, userUid ?? uid);
|
||||
handsSystem.PickupOrDrop(userUid, item, handsComp: handsComp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CriminalRecordsConsoleComponent> 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<CriminalRecordsConsoleComponent> 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
|
||||
|
|
|
|||
|
|
@ -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<AfterGeneralRecordCreatedEvent>(OnGeneralRecordCreated);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CriminalRecordChangedEvent>(OnRecordChanged);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CartridgeUiReadyEvent>(OnCartridgeUiReady);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CriminalHistoryAddedEvent>(OnHistoryAdded);
|
||||
SubscribeLocalEvent<WantedListCartridgeComponent, CriminalHistoryRemovedEvent>(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.
|
||||
/// </summary>
|
||||
/// <returns>True if the status is changed, false if not</returns>
|
||||
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<CriminalRecord>(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
|
|||
/// <summary>
|
||||
/// Sets the status without checking previous status or reason nullability.
|
||||
/// </summary>
|
||||
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<WantedListCartridgeComponent>();
|
||||
while (query.MoveNext(out var readerUid, out _))
|
||||
{
|
||||
RaiseLocalEvent(readerUid, ref args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -76,15 +95,23 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
|||
return false;
|
||||
|
||||
record.History.Add(entry);
|
||||
|
||||
var args = new CriminalHistoryAddedEvent(entry);
|
||||
var query = EntityQueryEnumerator<WantedListCartridgeComponent>();
|
||||
while (query.MoveNext(out var readerUid, out _))
|
||||
{
|
||||
RaiseLocalEvent(readerUid, ref args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and tries to add a history entry using the current time.
|
||||
/// </summary>
|
||||
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<WantedListCartridgeComponent>();
|
||||
while (query.MoveNext(out var readerUid, out _))
|
||||
{
|
||||
RaiseLocalEvent(readerUid, ref args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnRecordChanged(Entity<WantedListCartridgeComponent> ent, ref CriminalRecordChangedEvent args) =>
|
||||
StateChanged(ent);
|
||||
|
||||
private void OnHistoryAdded(Entity<WantedListCartridgeComponent> ent, ref CriminalHistoryAddedEvent args) =>
|
||||
StateChanged(ent);
|
||||
|
||||
private void OnHistoryRemoved(Entity<WantedListCartridgeComponent> ent, ref CriminalHistoryRemovedEvent args) =>
|
||||
StateChanged(ent);
|
||||
|
||||
private void StateChanged(Entity<WantedListCartridgeComponent> ent)
|
||||
{
|
||||
if (Comp<CartridgeComponent>(ent).LoaderUid is not { } loaderUid)
|
||||
return;
|
||||
|
||||
UpdateReaderUi(ent, loaderUid);
|
||||
}
|
||||
|
||||
private void OnCartridgeUiReady(Entity<WantedListCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
|
||||
{
|
||||
UpdateReaderUi(ent, args.Loader);
|
||||
}
|
||||
|
||||
private void UpdateReaderUi(Entity<WantedListCartridgeComponent> ent, EntityUid loaderUid)
|
||||
{
|
||||
if (_station.GetOwningStation(ent) is not { } station)
|
||||
return;
|
||||
|
||||
var records = _records.GetRecordsOfType<CriminalRecord>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Content.Server.Explosion.Components;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of tiles of each grid that are directly adjacent to space, along with the directions that face space.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the main explosion generating function.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<TransformComponent> _transformQuery;
|
||||
private EntityQuery<FlammableComponent> _flammableQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<ProjectileComponent> _projectileQuery;
|
||||
|
|
@ -103,7 +104,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||
InitAirtightMap();
|
||||
InitVisuals();
|
||||
|
||||
_transformQuery = GetEntityQuery<TransformComponent>();
|
||||
_flammableQuery = GetEntityQuery<FlammableComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_projectileQuery = GetEntityQuery<ProjectileComponent>();
|
||||
|
|
@ -141,15 +141,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||
args.DamageCoefficient *= modifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an entity with an explosive component, spawn the appropriate explosion.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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).
|
||||
/// </remarks>
|
||||
public void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null)
|
||||
/// <inheritdoc/>
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||
args.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args)
|
||||
{
|
||||
if (!TryComp<SubdermalImplantComponent>(uid, out var implanted))
|
||||
|
|
@ -230,7 +231,7 @@ namespace Content.Server.Explosion.EntitySystems
|
|||
private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard))
|
||||
Trigger(uid);
|
||||
Trigger(uid, args.OtherEntity);
|
||||
}
|
||||
|
||||
private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ namespace Content.Server.Flash
|
|||
}
|
||||
}
|
||||
|
||||
public void FlashArea(Entity<FlashComponent?> 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<FlashComponent?> 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);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||
SetAppearance(ent.Owner, MicrowaveVisualState.Cooking, microwaveComponent);
|
||||
|
||||
microwaveComponent.PlayingStream =
|
||||
_audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5)).Value.Entity;
|
||||
_audio.PlayPvs(microwaveComponent.LoopingSound, ent, AudioParams.Default.WithLoop(true).WithMaxDistance(5))?.Entity;
|
||||
}
|
||||
|
||||
private void OnCookStop(Entity<ActiveMicrowaveComponent> ent, ref ComponentShutdown args)
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
|||
active.Program = program;
|
||||
|
||||
reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid,
|
||||
AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier)).Value.Entity; //slightly higher pitched
|
||||
AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier))?.Entity; //slightly higher pitched
|
||||
_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
|
||||
new ReagentGrinderWorkStartedMessage(program));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ public sealed class MechGrabberSystem : EntitySystem
|
|||
return;
|
||||
|
||||
args.Handled = true;
|
||||
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid).Value.Entity;
|
||||
component.AudioStream = _audio.PlayPvs(component.GrabSound, uid)?.Entity;
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.GrabDelay, new GrabberDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
BreakOnMove = true
|
||||
|
|
|
|||
|
|
@ -1,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<CartridgeComponent>(entity, out var cartridge) &&
|
||||
cartridge.InstallationStatus is not InstallationStatus.Cartridge)
|
||||
return 0;
|
||||
|
||||
// check if needed target alive
|
||||
if (condition.CheckAlive)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -102,10 +102,6 @@ public sealed class TegSystem : EntitySystem
|
|||
|
||||
private void GeneratorUpdate(EntityUid uid, TegGeneratorComponent component, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
var tegGroup = GetNodeGroup(uid);
|
||||
if (tegGroup is not { IsFullyBuilt: true })
|
||||
return;
|
||||
|
||||
var supplier = Comp<PowerSupplierComponent>(uid);
|
||||
var powerReceiver = Comp<ApcPowerReceiverComponent>(uid);
|
||||
if (!powerReceiver.Powered)
|
||||
|
|
@ -114,6 +110,10 @@ public sealed class TegSystem : EntitySystem
|
|||
return;
|
||||
}
|
||||
|
||||
var tegGroup = GetNodeGroup(uid);
|
||||
if (tegGroup is not { IsFullyBuilt: true })
|
||||
return;
|
||||
|
||||
var circA = tegGroup.CirculatorA!.Owner;
|
||||
var circB = tegGroup.CirculatorB!.Owner;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ using Content.Server.Administration.Logs;
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Respawn;
|
||||
using Content.Shared.Station.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Respawn;
|
||||
|
||||
|
|
@ -179,7 +178,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
|
|||
|
||||
foreach (var newTileRef in grid.GetTilesIntersecting(circle))
|
||||
{
|
||||
if (newTileRef.IsSpace(_tileDefinitionManager) || newTileRef.IsBlockedTurf(true) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget))
|
||||
if (newTileRef.IsSpace(_tileDefinitionManager) || _turf.IsTileBlocked(newTileRef, CollisionGroup.MobMask) || !_atmosphere.IsTileMixtureProbablySafe(targetGrid, targetMap, mapTarget))
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public sealed class ContainmentFieldGeneratorSystem : EntitySystem
|
|||
SubscribeLocalEvent<ContainmentFieldGeneratorComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
SubscribeLocalEvent<ContainmentFieldGeneratorComponent, ComponentRemove>(OnComponentRemoved);
|
||||
SubscribeLocalEvent<ContainmentFieldGeneratorComponent, EventHorizonAttemptConsumeEntityEvent>(PreventBreach);
|
||||
SubscribeLocalEvent<ContainmentFieldGeneratorComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
|
@ -61,6 +62,12 @@ public sealed class ContainmentFieldGeneratorSystem : EntitySystem
|
|||
|
||||
#region Events
|
||||
|
||||
private void OnMapInit(Entity<ContainmentFieldGeneratorComponent> generator, ref MapInitEvent args)
|
||||
{
|
||||
if (generator.Comp.Enabled)
|
||||
ChangeFieldVisualizer(generator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A generator receives power from a source colliding with it.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[Access(typeof(SharedAnomalySystem))]
|
||||
[Access(typeof(SharedAnomalySystem), typeof(SharedInnerBodyAnomalySystem))]
|
||||
public sealed partial class AnomalyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -184,21 +184,21 @@ public sealed partial class AnomalyComponent : Component
|
|||
/// <summary>
|
||||
/// The minimum amount of research points generated per second
|
||||
/// </summary>
|
||||
[DataField("minPointsPerSecond")]
|
||||
[DataField]
|
||||
public int MinPointsPerSecond = 10;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of research points generated per second
|
||||
/// This doesn't include the point bonus for being unstable.
|
||||
/// </summary>
|
||||
[DataField("maxPointsPerSecond")]
|
||||
[DataField]
|
||||
public int MaxPointsPerSecond = 70;
|
||||
|
||||
/// <summary>
|
||||
/// The multiplier applied to the point value for the
|
||||
/// anomaly being above the <see cref="GrowthThreshold"/>
|
||||
/// </summary>
|
||||
[DataField("growingPointMultiplier")]
|
||||
[DataField]
|
||||
public float GrowingPointMultiplier = 1.5f;
|
||||
#endregion
|
||||
|
||||
|
|
@ -252,10 +252,13 @@ public sealed partial class AnomalyComponent : Component
|
|||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// An anomaly within the body of a living being. Controls the ability to return to the standard state.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedInnerBodyAnomalySystem))]
|
||||
public sealed partial class InnerBodyAnomalyComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Injected;
|
||||
|
||||
/// <summary>
|
||||
/// A prototype of an entity whose components will be added to the anomaly host **AND** then removed at the right time
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId? InjectionProto;
|
||||
|
||||
/// <summary>
|
||||
/// Duration of stun from the effect of the anomaly
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float StunDuration = 4f;
|
||||
|
||||
/// <summary>
|
||||
/// A message sent in chat to a player who has become infected by an anomaly
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? StartMessage = null;
|
||||
|
||||
/// <summary>
|
||||
/// A message sent in chat to a player who has cleared an anomaly
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? EndMessage = "inner-anomaly-end-message";
|
||||
|
||||
/// <summary>
|
||||
/// Sound, playing on becoming anomaly
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? StartSound = new SoundPathSpecifier("/Audio/Effects/inneranomaly.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Used to display messages to the player about their level of disease progression
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LastSeverityInformed = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// The fallback sprite to be added on the original entity. Allows you to visually identify the feature and type of anomaly to other players
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public SpriteSpecifier? FallbackSprite = null;
|
||||
|
||||
/// <summary>
|
||||
/// Ability to use unique sprites for different body types
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<ProtoId<BodyPrototype>, SpriteSpecifier> SpeciesSprites = new();
|
||||
|
||||
/// <summary>
|
||||
/// The key of the entity layer into which the sprite will be inserted
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string LayerMap = "inner_anomaly_layer";
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Anomaly.Components;
|
||||
|
||||
/// <summary>
|
||||
/// On contact with an entity, if it meets the conditions, it will transfer the specified components to it
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SharedInnerBodyAnomalySystem))]
|
||||
public sealed partial class InnerBodyAnomalyInjectorComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// components that will be automatically removed after “curing”
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ComponentRegistry InjectionComponents = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace Content.Shared.Anomaly.Effects;
|
||||
|
||||
public abstract class SharedInnerBodyAnomalySystem : EntitySystem
|
||||
{
|
||||
}
|
||||
|
|
@ -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
|
|||
/// <param name="uid">The anomaly being shut down</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="supercritical">Whether or not the anomaly ended via supercritical event</param>
|
||||
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false)
|
||||
/// <param name="spawnCore">Create anomaly cores based on the result of completing an anomaly?</param>
|
||||
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<AnomalySupercriticalComponent>(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -458,3 +465,5 @@ public partial record struct AnomalySpawnSettings()
|
|||
/// </summary>
|
||||
public bool SpawnOnSeverityChanged { get; set; } = false;
|
||||
}
|
||||
|
||||
public sealed partial class ActionAnomalyPulseEvent : InstantActionEvent { }
|
||||
|
|
|
|||
|
|
@ -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<BuckleComponent> ent, ref InteractHandEvent args)
|
||||
|
|
|
|||
|
|
@ -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<WantedRecord> records) : BoundUserInterfaceState
|
||||
{
|
||||
public List<WantedRecord> Records = records;
|
||||
}
|
||||
|
|
@ -359,39 +359,76 @@ namespace Content.Shared.Containers.ItemSlots
|
|||
/// Useful for predicted interactions
|
||||
/// </param>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsertEmpty(Entity<ItemSlotsComponent?> ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false)
|
||||
public bool TryInsertEmpty(Entity<ItemSlotsComponent?> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get any slot that the <paramref name="item"/> can be inserted into.
|
||||
/// </summary>
|
||||
/// <param name="ent">Entity that <paramref name="item"/> is being inserted into.</param>
|
||||
/// <param name="item">Entity being inserted into <paramref name="ent"/>.</param>
|
||||
/// <param name="userEnt">Entity inserting <paramref name="item"/> into <paramref name="ent"/>.</param>
|
||||
/// <param name="itemSlot">The ItemSlot on <paramref name="ent"/> to insert <paramref name="item"/> into.</param>
|
||||
/// <param name="emptyOnly"> True only returns slots that are empty.
|
||||
/// False returns any slot that is able to receive <paramref name="item"/>.</param>
|
||||
/// <returns>True when a slot is found. Otherwise, false.</returns>
|
||||
public bool TryGetAvailableSlot(Entity<ItemSlotsComponent?> ent,
|
||||
EntityUid item,
|
||||
Entity<HandsComponent?>? 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<ItemSlot>();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ public sealed record CriminalRecord
|
|||
[DataField]
|
||||
public string? Reason;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the person who changed the status.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? InitiatorName;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public record struct CrimeHistory(TimeSpan AddTime, string Crime);
|
||||
public record struct CrimeHistory(TimeSpan AddTime, string Crime, string? InitiatorName);
|
||||
|
|
|
|||
|
|
@ -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<CrimeHistory> history)
|
||||
{
|
||||
public GeneralStationRecord TargetInfo = targetInfo;
|
||||
public SecurityStatus Status = status;
|
||||
public string? Reason = reason;
|
||||
public string? Initiator = initiator;
|
||||
public List<CrimeHistory> History = history;
|
||||
};
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct CriminalRecordChangedEvent(CriminalRecord Record);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct CriminalHistoryAddedEvent(CrimeHistory History);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct CriminalHistoryRemovedEvent(CrimeHistory History);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an explosion that can be spawned by this entity. The explosion itself is spawned via <see
|
||||
|
|
@ -12,31 +11,27 @@ namespace Content.Server.Explosion.Components;
|
|||
/// The total intensity may be overridden by whatever system actually calls TriggerExplosive(), but this
|
||||
/// component still determines the explosion type and other properties.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, Access(typeof(SharedExplosionSystem))]
|
||||
public sealed partial class ExplosiveComponent : Component
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("explosionType", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
|
||||
public string ExplosionType = default!;
|
||||
[DataField(required: true)]
|
||||
public ProtoId<ExplosionPrototype> ExplosionType = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxIntensity")]
|
||||
[DataField]
|
||||
public float MaxIntensity = 4;
|
||||
|
||||
/// <summary>
|
||||
/// How quickly the intensity drops off as you move away from the epicenter.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("intensitySlope")]
|
||||
[DataField]
|
||||
public float IntensitySlope = 1;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,38 +42,34 @@ public sealed partial class ExplosiveComponent : Component
|
|||
/// This number can be overridden by passing optional argument to <see
|
||||
/// cref="ExplosionSystem.TriggerExplosive"/>.
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("totalIntensity")]
|
||||
[DataField]
|
||||
public float TotalIntensity = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("tileBreakScale")]
|
||||
[DataField]
|
||||
public float TileBreakScale = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxTileBreak")]
|
||||
[DataField]
|
||||
public int MaxTileBreak = int.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this explosive should be able to create a vacuum by breaking tiles.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("canCreateVacuum")]
|
||||
[DataField]
|
||||
public bool CanCreateVacuum = true;
|
||||
|
||||
/// <summary>
|
||||
/// An override for whether or not the entity should be deleted after it explodes.
|
||||
/// If null, the system calling the explode method handles it.
|
||||
/// </summary>
|
||||
[DataField("deleteAfterExplosion")]
|
||||
[DataField]
|
||||
public bool? DeleteAfterExplosion;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1,25 +1,40 @@
|
|||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Explosion.Components;
|
||||
|
||||
namespace Content.Shared.Explosion.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Lets code in shared trigger explosions and handles explosion resistance examining.
|
||||
/// All processing is still done clientside.
|
||||
/// </summary>
|
||||
public abstract class SharedExplosionSystem : EntitySystem
|
||||
{
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ExplosionResistanceComponent, ArmorExamineEvent>(OnArmorExamine);
|
||||
}
|
||||
|
||||
private void OnArmorExamine(EntityUid uid, ExplosionResistanceComponent component, ref ArmorExamineEvent args)
|
||||
private void OnArmorExamine(Entity<ExplosionResistanceComponent> 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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an entity with an explosive component, spawn the appropriate explosion.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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).
|
||||
/// </remarks>
|
||||
public virtual void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StatusEffectPrototype> FlashedKey = "Flashed";
|
||||
|
||||
public virtual void FlashArea(Entity<FlashComponent?> source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null)
|
||||
{
|
||||
[ValidatePrototypeId<StatusEffectPrototype>]
|
||||
public const string FlashedKey = "Flashed";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ public sealed partial class DrainComponent : Component
|
|||
[DataField]
|
||||
public Entity<SolutionComponent>? Solution = null;
|
||||
|
||||
[DataField("accumulator")]
|
||||
[DataField]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Does this drain automatically absorb surrouding puddles? Or is it a drain designed to empty
|
||||
/// solutions in it manually?
|
||||
/// solutions in it manually?
|
||||
/// </summary>
|
||||
[DataField("autoDrain"), ViewVariables(VVAccess.ReadOnly)]
|
||||
[DataField]
|
||||
public bool AutoDrain = true;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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.
|
||||
/// </summary>
|
||||
[DataField("unitsPerSecond")]
|
||||
[DataField]
|
||||
public float UnitsPerSecond = 6f;
|
||||
|
||||
/// <summary>
|
||||
/// How many units are ejected from the buffer per second.
|
||||
/// </summary>
|
||||
[DataField("unitsDestroyedPerSecond")]
|
||||
[DataField]
|
||||
public float UnitsDestroyedPerSecond = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// How many (unobstructed) tiles away the drain will
|
||||
/// drain puddles from.
|
||||
/// </summary>
|
||||
[DataField("range"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float Range = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// How often in seconds the drain checks for puddles around it.
|
||||
/// If the EntityQuery seems a bit unperformant this can be increased.
|
||||
/// </summary>
|
||||
[DataField("drainFrequency")]
|
||||
[DataField]
|
||||
public float DrainFrequency = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// How much time it takes to unclog it with a plunger
|
||||
/// </summary>
|
||||
[DataField("unclogDuration"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float UnclogDuration = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// What's the probability of uncloging on each try
|
||||
/// </summary>
|
||||
[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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Content.Shared.Interaction.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the target when failing to pet/hug something.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct InteractionFailureEvent(EntityUid User);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Content.Shared.Interaction.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the target when successfully petting/hugging something.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct InteractionSuccessEvent(EntityUid User);
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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<PlaceableSurfaceComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
|
||||
SubscribeLocalEvent<PlaceableSurfaceComponent, StorageInteractUsingAttemptEvent>(OnStorageInteractUsingAttempt);
|
||||
SubscribeLocalEvent<PlaceableSurfaceComponent, StorageAfterOpenEvent>(OnStorageAfterOpen);
|
||||
SubscribeLocalEvent<PlaceableSurfaceComponent, StorageAfterCloseEvent>(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<PlaceableSurfaceComponent> ent, ref StorageInteractUsingAttemptEvent args)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnStorageAfterOpen(Entity<PlaceableSurfaceComponent> ent, ref StorageAfterOpenEvent args)
|
||||
{
|
||||
SetPlaceable(ent.Owner, true, ent.Comp);
|
||||
}
|
||||
|
||||
private void OnStorageAfterClose(Entity<PlaceableSurfaceComponent> ent, ref StorageAfterCloseEvent args)
|
||||
{
|
||||
SetPlaceable(ent.Owner, false, ent.Comp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public sealed partial class ContainmentFieldGeneratorComponent : Component
|
|||
/// <summary>
|
||||
/// Is the generator toggled on?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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<EntityUid> _toRemove = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -32,17 +33,27 @@ namespace Content.Shared.StatusEffect
|
|||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
var enumerator = EntityQueryEnumerator<ActiveStatusEffectsComponent, StatusEffectsComponent>();
|
||||
_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<ActiveStatusEffectsComponent>(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<ActiveStatusEffectsComponent>(uid);
|
||||
else
|
||||
EnsureComp<ActiveStatusEffectsComponent>(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<T>(uid))
|
||||
{
|
||||
var comp = EntityManager.AddComponent<T>(uid);
|
||||
status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(comp.GetType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!TryAddStatusEffect(uid, key, time, refresh, status))
|
||||
return false;
|
||||
|
||||
if (HasComp<T>(uid))
|
||||
return true;
|
||||
|
||||
EntityManager.AddComponent<T>(uid);
|
||||
status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName<T>();
|
||||
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!
|
||||
/// </remarks>
|
||||
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
|
|||
/// <param name="uid">The entity to check on.</param>
|
||||
/// <param name="key">The status effect ID to check for</param>
|
||||
/// <param name="status">The status effect component, should you already have it.</param>
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -487,9 +487,6 @@ public abstract class SharedEntityStorageSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
if (TryComp<PlaceableSurfaceComponent>(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,7 +364,9 @@ public abstract class SharedStorageSystem : EntitySystem
|
|||
if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false))
|
||||
return;
|
||||
|
||||
if (HasComp<PlaceableSurfaceComponent>(uid))
|
||||
var attemptEv = new StorageInteractUsingAttemptEvent();
|
||||
RaiseLocalEvent(uid, ref attemptEv);
|
||||
if (attemptEv.Cancelled)
|
||||
return;
|
||||
|
||||
PlayerInsertHeldEntity((uid, storageComp), args.User);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
source: https://zvukipro.com/predmet/158-zvuk-elektrichestva.html
|
||||
|
|
|
|||
BIN
Resources/Audio/Effects/inneranomaly.ogg
Normal file
BIN
Resources/Audio/Effects/inneranomaly.ogg
Normal file
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
ui-actionslot-charges = Uses left: {$charges}
|
||||
|
||||
ui-actionslot-duration = [color=#a10505] {$duration} sec cooldown ({$timeLeft} sec remaining)[/color]
|
||||
|
|
|
|||
17
Resources/Locale/en-US/_strings/anomaly/inner_anomaly.ftl
Normal file
17
Resources/Locale/en-US/_strings/anomaly/inner_anomaly.ftl
Normal file
|
|
@ -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!
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = <unknown officer>
|
||||
criminal-records-console-unknown-officer = <unknown>
|
||||
|
||||
## Filters
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
9
Resources/Prototypes/Actions/anomaly.yml
Normal file
9
Resources/Prototypes/Actions/anomaly.yml
Normal file
|
|
@ -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
|
||||
|
|
@ -331,6 +331,7 @@
|
|||
- id: RubberStampHos
|
||||
- id: SecurityTechFabCircuitboard
|
||||
- id: WeaponDisabler
|
||||
- id: WantedListCartridge
|
||||
- id: FlippoLighterSunriseHos # Sunrise-Flippo
|
||||
|
||||
# Hardsuit table, used for suit storage as well
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue