Merge branch 'master' into euphoriacardport

Signed-off-by: freezer dog <iamabanana372456@gmail.com>
This commit is contained in:
freezer dog 2026-07-10 21:33:22 -04:00 committed by GitHub
commit af2b242976
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
863 changed files with 12018 additions and 3847 deletions

View File

@ -31,7 +31,7 @@ Examples of Pets include, but are not limited to:
:* hand-held "personal AI" devices
== Synthetic ==
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage. Irrevocable destruction of a Synthetic may leave the offender liable to the crime of Grand Sabotage.
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage.
Examples of Synthetics include, but are not limited to:
:* Crew-constructed Cyborgs
:* The Station AI
@ -86,7 +86,7 @@ Any individual requiring any above item in the course of their job, or for regul
Should the holder commit crimes through the use of such authorized items, the authorizing party may be found guilty of Accessory to said crimes, in addition to Endangerment and Abuse of Power where applicable. The Commanding Officer is also authorized to supervise the approval and revocation of documentation as they see fit.
== Controlled Substances ==
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a perscription or special dispensation from the Chief Medical Officer. The Psychologist is also permitted to grant prescriptions for psychoactive drugs. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a prescription or special dispensation from the Chief Medical Officer. Medical personnel are also permitted to [[Standard Operating Procedure#Prescribing Medication|prescribe medication]] for chronic illnesses or mental health. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
A list of such Substances follow:
@ -370,6 +370,38 @@ Given good medical reason and with documentation, the Chief Medical Officer of a
In order for a medical evaluation to be considered lawfully applicable, the CMO and relevant head of department must both agree that the specified crewmate is unfit for duty in order to proceed with the demotion. In the case of the Commanding Officer, Central Command should be contacted as soon as possible.
==Prescribing Medication==
In the event that a patient requires stronger medication outside of medbay, Medical personnel have the ability to prescribe medicine to a patient. Prescriptions must be written on valid prescription paper, as well as signed and stamped with a valid medical personnel's stamp and signature. Any such document must state the substance(s) being prescribed as well as the amount(s) where applicable. The chief medical officer also has the right to revoke any prescription with valid medical reasoning. Prescriptions are not valid if they are both intended for and signed and stamped by the same person (e.g. You cannot prescribe something to yourself as a doctor).
=== Prescription Medicine ===
Prescriptions that are valid with any medical personnel's stamp and signature are as follows:
* diphenylmethylamine
* equilibrazine
* tranquinase
* neurozenium
* serenitol
* soretizone
Highly-addictive mental health prescriptions that are only valid with the chief medical officer's or psychologist's stamp and signature are as follows:
* blissifylovene
* happiness
Highly-addictive or potentially-harmful prescriptions that are only valid with the chief medical officer's stamp and signature are as follows:
* agonolexyne
* desoxyephedrine
* nocturine
=== Dangerous Chemical Authorizations ===
Other dangerous chemicals that may be authorized and given out ONLY with valid written permission with the chief medical officer's stamp are as follows:
* mindbreaker, heartbreaker, and mute toxins
* amatoxin and other dangerous poisons
* phlogiston, napalm, and chlorine trifluoride
* vestine and romerol
* Any other [[Standard Operating Procedure#Controlled Substances|controlled substances]] not listed here
The chief medical officer is also required to inform and/or forward authorization of any dangerous chemical to Security.
==Quarantine==
Medical staff are afforded the right to quarantine individuals that currently suffer from infectious or contagious diseases, and may confine them within pre-designated quarantine areas in the course of treatment, until the point that they are no longer infectious or harmful to the rest of the crew.

View File

@ -1,8 +1,10 @@
using Content.Shared.MedicalScanner;
using Content.Shared._DV.MedicalRecords;
using JetBrains.Annotations; // DeltaV - Medical Records
using Content.Shared._DV.Medical; // DeltaV
using Content.Shared._DV.MedicalRecords; // DeltaV
using JetBrains.Annotations; // DeltaV
using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI
{
[UsedImplicitly]
@ -22,6 +24,7 @@ namespace Content.Client.HealthAnalyzer.UI
_window = this.CreateWindow<HealthAnalyzerWindow>();
_window.HealthAnalyzer.OnTriageStatusChanged += SendTriageStatusMessage; // DeltaV - Medical Records
_window.HealthAnalyzer.OnClaimPatient += SendTriageClaimMessage; // DeltaV - Medical Records
_window.HealthAnalyzer.OnPrintMedTekRecord += () => SendMessage(new HealthAnalyzerPrintReportMessage()); // DeltaV - MedTekReports
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
}

View File

@ -1,7 +1,9 @@
<BoxContainer
xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client._DV.UserInterfaces"
xmlns:xe="clr-namespace:Content.Client.UserInterface.XamlExtensions"
VerticalExpand="True"
Orientation="Vertical">
Orientation="Vertical"> <!-- DeltaV - added ui and xe namespaces -->
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
@ -11,7 +13,8 @@
Margin="0 0 0 5"
Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
<TextureRect Name="NoDataTex" Access="Public" SetSize="96 96" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/> <!-- DeltaV - Size changed to 96-->
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
<RichTextLabel Name="NameLabel" SetWidth="150" />
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
@ -22,6 +25,14 @@
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
<!-- BEGIN DeltaV - MedTek Reports -->
<BoxContainer HorizontalExpand="False"
Orientation="Vertical"
Name="PrintButtonsContainer"
Margin="0 0 5 0">
<ui:IconButton Name="PrintReportButton" Icon="{xe:Tex '/Textures/_DV/Interface/VerbIcons/print.png'}" />
</BoxContainer>
<!-- END DeltaV -->
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />

View File

@ -49,6 +49,8 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
public event Action<TriageStatus>? OnTriageStatusChanged;
public event Action? OnClaimPatient;
// End DeltaV - Medical Records
public Action? OnPrintMedTekRecord; // DeltaV - MedTek Reports
public HealthAnalyzerControl()
{
@ -84,6 +86,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
StatusBox.Children.Last().RemoveStyleClass("ButtonSquare");
ClaimButton.OnPressed += _ => OnClaimPatient?.Invoke();
// End DeltaV - Medical Records
PrintReportButton.OnPressed += (_) => OnPrintMedTekRecord?.Invoke(); // DeltaV - MedTek Reports
}
public void Populate(HealthAnalyzerUiState state)
@ -109,7 +112,12 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
ScanModeLabel.FontColorOverride = state.ScanMode.HasValue && state.ScanMode.Value ? Color.Green : Color.Red;
PrintReportButton.Disabled = state is { ScanMode: false }; // DeltaV
// Patient Information
SpriteView.SetEntity(target.Value);
SpriteView.Visible = state.ScanMode.HasValue && state.ScanMode.Value;
NoDataTex.Visible = !SpriteView.Visible;
var name = new FormattedMessage();
name.PushColor(Color.White);
@ -255,7 +263,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
("damageGroup", _prototypes.Index(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
@ -270,7 +278,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
GroupsContainer.AddChild(groupContainer);
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
var group = _prototypes.Index(damageGroupId);
foreach (var type in group.DamageTypes)
{

View File

@ -93,7 +93,7 @@ namespace Content.Client.Lobby.UI
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
{
// DeltaV - Ignore hidden species.
if (character is HumanoidCharacterProfile hcp && SpeciesHiderSystem.IsHidden(hcp.Species.Id))
if (character is { } hcp && SpeciesHiderSystem.IsHidden(hcp.Species.Id))
continue;
// End DeltaV
numberOfFullSlots++;

View File

@ -1,6 +1,7 @@
using System.Globalization;
using System.Linq;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._DV.Species;
using Content.Shared.Guidebook;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
@ -159,6 +160,8 @@ public sealed partial class HumanoidProfileEditor
for (var i = 0; i < _species.Count; i++)
{
if (SpeciesHiderSystem.IsHidden(_species[i].ID)) // Delta V - Don't add hidden species
continue;
var name = Loc.GetString(_species[i].Name);
SpeciesButton.AddItem(name, i);

View File

@ -1,4 +1,6 @@
using System.Linq; // DeltaV
using Content.Client.Pinpointer.UI;
using Content.Client.Station; // DeltaV
using Robust.Client.Graphics;
using Robust.Client.Player; // DeltaV
using Robust.Client.UserInterface.Controls;
@ -10,6 +12,7 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
{
[Dependency] private IPlayerManager _playerManager = default!; // DeltaV
private readonly SharedTransformSystem _xform; // DeltaV
private readonly StationSystem _station; // DeltaV
public NetEntity? Focus;
public Dictionary<NetEntity, string> LocalizedNames = new();
@ -21,6 +24,7 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
{
IoCManager.InjectDependencies(this); // DeltaV - Gotta inject!
_xform = EntManager.System<SharedTransformSystem>(); // DeltaV - Gotta inject!
_station = EntManager.System<StationSystem>(); // DeltaV - Gotta Inject!
WallColor = new Color(192, 122, 196);
TileColor = new(71, 42, 72);
@ -97,7 +101,12 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
if (!MapUid.HasValue && _playerManager.LocalEntity is { } player)
{
MapUid = _xform.GetGrid(player);
forceMapUpdate = true;
// If it still doesn't have a value, just use the largest grid of the station.
if (!MapUid.HasValue)
MapUid = _station.GetLargestGrid(_station.GetStations().First());
forceMapUpdate = MapUid.HasValue;
}
base.Draw(handle);

View File

@ -18,6 +18,21 @@
</PanelContainer>
</controls:StripeBack>
<!-- BEGIN DeltaV - Station/Shuttle Map Buttons -->
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Name="StationMapButton"
ToggleMode="True"
Text="{Loc crew-monitoring-ui-button-station-map}"
StyleClasses="OpenRight"
HorizontalExpand="True" />
<Button Name="ShuttleMapButton"
ToggleMode="True"
Text="{Loc crew-monitoring-ui-button-shuttle-map}"
StyleClasses="OpenLeft"
HorizontalExpand="True" />
</BoxContainer>
<!-- END DeltaV -->
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
PlaceHolder="{Loc crew-monitoring-ui-filter-line-placeholder}" />

View File

@ -45,6 +45,9 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
ShuttleMap.NavMap = NavMap; // DeltaV - crew monitoring radar
StationMapButton.OnPressed += _ => EnableShuttleMap(false); // DeltaV - crew monitoring radar
ShuttleMapButton.OnPressed += _ => EnableShuttleMap(true); // DeltaV - crew monitoring radar
}
public void Set(string stationName, EntityUid? mapUid)
@ -57,6 +60,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
// Begin DeltaV - handle navmap visibility based on current position
// else
// NavMap.Visible = false;
EnableShuttleMap(!NavMap.MapUid.HasValue);
// End DeltaV - handle navmap visibility based on current position
StationName.AddStyleClass("LabelBig");
@ -68,28 +72,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
{
base.FrameUpdate(args);
// Begin DeltaV - handle navmap visibility based on current position
var ent = ShuttleMap.Owner;
// If an entity is being tracked, use them as the focus, since they may be off-station
// and we want to use the nav map if they are.
if (_trackedEntity is { } trackedEntity)
ent = _entManager.GetEntity(trackedEntity);
// We need to make sure the entity is valid because for some reason, it *could* be an
// invalid entity and GetOwningStation will throw an exception.
if (ent.HasValue && ent.Value.IsValid() && _station.GetOwningStation(ent) is not null)
{
NavMap.Visible = true;
ShuttleMap.Visible = false;
}
else
{
NavMap.Visible = false;
ShuttleMap.Visible = true;
}
// End DeltaV - handle navmap visibility based on current position
if (_tryToScrollToListFocus)
TryToScrollToFocus();
}
@ -390,6 +372,18 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
UpdateSensorsTable(_trackedEntity, prevTrackedEntity);
}
/// <summary>
/// DeltaV - If true, will show the shuttle map. If false, will show the normal station map.
/// </summary>
private void EnableShuttleMap(bool visible)
{
NavMap.Visible = !visible;
StationMapButton.Pressed = !visible;
ShuttleMap.Visible = visible;
ShuttleMapButton.Pressed = visible;
}
private void UpdateSensorsTable(NetEntity? currTrackedEntity, NetEntity? prevTrackedEntity)
{
foreach (var sensor in SensorsTable.Children)

View File

@ -11,6 +11,7 @@ using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player; // DeltaV
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using static Robust.Shared.Maths.Color;
@ -22,6 +23,7 @@ namespace Content.Client.Overlays;
/// </summary>
public sealed class EntityHealthBarOverlay : Overlay
{
[Dependency] private readonly IPlayerManager _player = default!; // DeltaV
private readonly IEntityManager _entManager;
private readonly IPrototypeManager _prototype;
@ -40,6 +42,8 @@ public sealed class EntityHealthBarOverlay : Overlay
public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype)
{
IoCManager.InjectDependencies(this); // DeltaV
_entManager = entManager;
_prototype = prototype;
_transform = _entManager.System<SharedTransformSystem>();
@ -69,6 +73,11 @@ public sealed class EntityHealthBarOverlay : Overlay
out var damageableComponent,
out var spriteComponent))
{
// BEGIN DeltaV - You can't see your own bar :godo:
if (_player.LocalEntity is { } player && player.Id == uid.Id)
continue;
// END DeltaV
if (statusIcon != null && !_statusIconSystem.IsVisible((uid, _entManager.GetComponent<MetaDataComponent>(uid)), statusIcon))
continue;

View File

@ -5,6 +5,7 @@ using Content.Client.UserInterface.RichText; // DeltaV - Limit what tags can be
using Content.Shared.GameTicking;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Content.Shared._Goobstation.StationReport; // Goob
using Robust.Client.UserInterface.RichText; // DeltaV - Limit what tags can be used in custom objective summaries
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@ -35,6 +36,7 @@ namespace Content.Client.RoundEnd
var roundEndTabs = new TabContainer();
roundEndTabs.AddChild(MakeRoundEndSummaryTab(gm, roundEnd, roundTimeSpan, roundId));
roundEndTabs.AddChild(MakePlayerManifestTab(info));
roundEndTabs.AddChild(MakeStationReportTab()); //goob
ContentsContainer.AddChild(roundEndTabs);
@ -181,6 +183,38 @@ namespace Content.Client.RoundEnd
return playerManifestTab;
}
private BoxContainer MakeStationReportTab() //Goob edit start
{
//gets the stationreport varibible and sets the station report tab text to it if the map doesn't have a tablet will say No station report submitted
var stationReportSystem = _entityManager.System<StationReportSystem>();
string stationReportText = stationReportSystem.StationReportText ?? Loc.GetString("no-station-report-summited");
var stationReportTab = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = Loc.GetString("round-end-summary-window-station-report-tab-title")
};
var StationReportContainerScrollbox = new ScrollContainer
{
VerticalExpand = true,
Margin = new Thickness(10),
HScrollEnabled = false,
};
var StationReportContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var StationReportLabel = new RichTextLabel();
var StationReportmessage = new FormattedMessage();
StationReportmessage.AddMarkupOrThrow(stationReportText);
StationReportLabel.SetMessage(StationReportmessage);
StationReportContainer.AddChild(StationReportLabel);
StationReportContainerScrollbox.AddChild(StationReportContainer);
stationReportTab.AddChild(StationReportContainerScrollbox);
return stationReportTab;
}
//Goob edit end
}
}

View File

@ -85,41 +85,40 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
{
case AsteroidOffering asteroid:
option.Title = Loc.GetString($"dungeon-config-proto-{asteroid.Id}");
break; // DeltaV: Skip ores since they aren't used with custom generation
// var layerKeys = asteroid.MarkerLayers.Keys.ToList();
// layerKeys.Sort();
//
// foreach (var resource in layerKeys)
// {
// var count = asteroid.MarkerLayers[resource];
//
// var container = new BoxContainer
// {
// Orientation = BoxContainer.LayoutOrientation.Horizontal,
// HorizontalExpand = true,
// };
//
// var resourceLabel = new Label
// {
// Text = Loc.GetString("salvage-magnet-resources",
// ("resource", resource)),
// HorizontalAlignment = Control.HAlignment.Left,
// };
//
// var countLabel = new Label
// {
// Text = Loc.GetString("salvage-magnet-resources-count", ("count", count)),
// HorizontalAlignment = Control.HAlignment.Right,
// HorizontalExpand = true,
// };
//
// container.AddChild(resourceLabel);
// container.AddChild(countLabel);
//
// option.AddContent(container);
// }
//
// break;
var layerKeys = asteroid.MarkerLayers.Keys.ToList();
layerKeys.Sort();
foreach (var resource in layerKeys)
{
var count = asteroid.MarkerLayers[resource];
var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
};
var resourceLabel = new Label
{
Text = Loc.GetString("salvage-magnet-resources",
("resource", resource)),
HorizontalAlignment = Control.HAlignment.Left,
};
var countLabel = new Label
{
Text = Loc.GetString("salvage-magnet-resources-count", ("count", count)),
HorizontalAlignment = Control.HAlignment.Right,
HorizontalExpand = true,
};
container.AddChild(resourceLabel);
container.AddChild(countLabel);
option.AddContent(container);
}
break;
case DebrisOffering debris:
option.Title = Loc.GetString($"salvage-magnet-debris-{debris.Id}");
break;

View File

@ -1,8 +1,9 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:chassisSpriteControls="clr-namespace:Content.Client._CD.Silicons.Borgs.UI"
Title="{Loc 'borg-select-type-menu-title'}"
SetSize="550 300">
SetSize="600 480">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
<!-- Left pane: selection of borg type -->
@ -29,6 +30,9 @@
<RichTextLabel Name="DescriptionLabel" VerticalExpand="True" VerticalAlignment="Top" />
</BoxContainer>
</Control>
<chassisSpriteControls:ChassisSpriteSelection Name="ChassisSpriteSelection"/> <!-- CD change -->
<controls:ConfirmButton Name="ConfirmTypeButton" Text="{Loc 'borg-select-type-menu-confirm'}"
Disabled="True" HorizontalAlignment="Right"
MinWidth="200" />

View File

@ -1,6 +1,7 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._CD.Silicons; // CosmicDrift
using Content.Shared.Guidebook;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
@ -29,6 +30,7 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
private BorgTypePrototype? _selectedBorgType;
public event Action<ProtoId<BorgTypePrototype>>? ConfirmedBorgType;
public event Action<EntityPrototype?>? ConfirmedBorgSubtype; // CosmicDrift event - borg subtypes
private static readonly List<ProtoId<GuideEntryPrototype>> GuidebookEntries = new() { "Cyborgs", "Robotics" };
@ -56,6 +58,13 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
ConfirmTypeButton.OnPressed += ConfirmButtonPressed;
HelpGuidebookIds = GuidebookEntries;
// Start CosmicDrift Changes - borg subtypes
ChassisSpriteSelection.SubtypeSelected += () =>
{
ConfirmTypeButton.Disabled = false;
};
// End CosmicDrift Changes - borg subtypes
}
private void UpdateInformation(BorgTypePrototype prototype)
@ -87,6 +96,14 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
NameLabel.Text = PrototypeName(prototype);
DescriptionLabel.Text = Loc.GetString($"borg-type-{prototype.ID}-desc");
ChassisView.SetPrototype(prototype.DummyPrototype);
// Start CosmicDrift Changes - borg subtypes
if (_selectedBorgType != null)
{
ConfirmTypeButton.Disabled = true;
ChassisSpriteSelection.Update(_selectedBorgType);
}
// End CosmicDrift Changes - borg subtypes
}
private void ConfirmButtonPressed(BaseButton.ButtonEventArgs obj)
@ -94,6 +111,7 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
if (_selectedBorgType == null)
return;
ConfirmedBorgSubtype?.Invoke(ChassisSpriteSelection.SubtypePrototype); // CosmicDrift
ConfirmedBorgType?.Invoke(_selectedBorgType);
}

View File

@ -1,4 +1,6 @@
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared._CD.Silicons; // CosmicDrift
using Content.Shared._CD.Silicons.Borgs; // CosmicDrift
using Content.Shared.Silicons.Borgs.Components;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
@ -26,5 +28,6 @@ public sealed class BorgSelectTypeUserInterface : BoundUserInterface
_menu = this.CreateWindow<BorgSelectTypeMenu>();
_menu.ConfirmedBorgType += prototype => SendPredictedMessage(new BorgSelectTypeMessage(prototype));
_menu.ConfirmedBorgSubtype += subtypePrototype => SendPredictedMessage(new BorgSelectSubtypeMessage(subtypePrototype?.ID)); // CosmicDrift - borg subtypes
}
}

View File

@ -1,9 +1,12 @@
using Content.Client.PDA; // DeltaV
using Content.Client.PDA; // DeltaV
using Content.Shared._CD.Silicons.Borgs; // CosmicDrift
using Content.Shared.Movement.Components;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.GameObjects;
using Robust.Shared.Serialization; // DeltaV
using Robust.Client.ResourceManagement; // CosmicDrift
using Robust.Shared.Serialization.TypeSerializers.Implementations; // CosmicDrift
using Robust.Shared.Timing; // CosmicDrift
namespace Content.Client.Silicons.Borgs;
@ -17,6 +20,7 @@ public sealed partial class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeS
[Dependency] private readonly BorgSystem _borgSystem = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IGameTiming _timing = default!; // CosmicDrift - borg subtypes
public override void Initialize()
{
@ -40,15 +44,20 @@ public sealed partial class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeS
Entity<BorgSwitchableTypeComponent> entity,
BorgTypePrototype prototype)
{
// Begin DeltaV Additions
if (prototype.ClientComponents is {} add)
EntityManager.AddComponents(entity, add);
// End DeltaV Additions
// Begin Afterlight Addition - added checks to stop sprite state errors
if (!_timing.IsFirstTimePredicted)
return;
if (TryComp<BorgSwitchableSubtypeComponent>(entity, out var subtype) &&
subtype.BorgSubtype != null)
{
var ev = new TypeTryingToUpdateVisualsEvent();
RaiseLocalEvent(entity, ref ev);
return;
}
// End Afterlight Additions - added checks to stop sprite state errors
if (TryComp(entity, out SpriteComponent? sprite))
{
// Begin DeltaV Additions - work around engine bug with AddComponents
((ISerializationHooks) sprite).AfterDeserialization();
// End DeltaV Additions
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.Body, prototype.SpriteBodyState);
_sprite.LayerSetRsiState((entity, sprite), BorgVisualLayers.LightStatus, prototype.SpriteToggleLightState);
}
@ -76,6 +85,26 @@ public sealed partial class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeS
}
// DeltaV - borg pdas
// Start CosmicDrift Changes - borg subtypes
if (prototype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = prototype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
// End CosmicDrift Changes - borg subtypes
base.UpdateEntityAppearance(entity, prototype);
}
}

View File

@ -29,11 +29,22 @@ public sealed class WelderStatusControl : PollingItemStatusControl<WelderStatusC
protected override Data PollData()
{
var (fuel, capacity) = _toolSystem.GetWelderFuelAndCapacity(_parent, _parent.Comp);
return new Data(fuel, capacity, _parent.Comp.Enabled);
return new Data(fuel, capacity, _parent.Comp.Enabled, _parent.Comp.OnlyDisplayFuel); // Monolith - Nanite Applicators
}
protected override void Update(in Data data)
{
// BEGIN Monolith - Nanite applicator
if (data.OnlyDisplayFuel)
{
_label.SetMarkup(Loc.GetString("welder-component-on-examine-less-detailed-message",
("colorName", data.Fuel < data.FuelCapacity / 4f ? "darkorange" : "orange"),
("fuelLeft", data.Fuel),
("fuelCapacity", data.FuelCapacity)));
return;
}
// END Monolith
_label.SetMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
("colorName", data.Fuel < data.FuelCapacity / 4f ? "darkorange" : "orange"),
("fuelLeft", data.Fuel),
@ -41,5 +52,5 @@ public sealed class WelderStatusControl : PollingItemStatusControl<WelderStatusC
("status", Loc.GetString(data.Lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message"))));
}
public record struct Data(FixedPoint2 Fuel, FixedPoint2 FuelCapacity, bool Lit);
public record struct Data(FixedPoint2 Fuel, FixedPoint2 FuelCapacity, bool Lit, bool OnlyDisplayFuel = false); // Monolith - Nanite Applicators
}

View File

@ -2,10 +2,12 @@
using Content.Client.Ghost;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Client.UserInterface.Systems.Ghost.Widgets;
using Content.Shared._DV.Ghost.Roles; // DeltaV - freeform ghosties
using Content.Shared.Ghost;
using Robust.Shared.Console; // Frontier
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Prototypes; // DeltaV - freeform ghosties
using Content.Shared._Corvax.Respawn; // Frontier
namespace Content.Client.UserInterface.Systems.Ghost;
@ -33,6 +35,7 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
// DeltaV
SubscribeNetworkEvent<RespawnResetEvent>(OnRespawnReseted);
SubscribeNetworkEvent<DVSpawnableGhostRoleCooldownUpdateEvent>(OnVentCritterCooldownUpdate); // DeltaV - freeform ghosties
}
private void OnScreenLoad()
@ -151,6 +154,8 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
Gui.RequestWarpsPressed += RequestWarps;
Gui.ReturnToBodyPressed += ReturnToBody;
Gui.GhostRolesPressed += GhostRolesPressed;
Gui.VentCritterPressed += OnVentCritterPressed; // DeltaV - freeform ghosties
Gui.VentCritterSelected += OnVentCritterSelected; // DeltaV - freeform ghosties
Gui.TargetWindow.WarpClicked += OnWarpClicked;
Gui.TargetWindow.OnGhostnadoClicked += OnGhostnadoClicked;
Gui.GhostRespawnPressed += GuiOnGhostRespawnPressed; // Frontier
@ -173,6 +178,8 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
Gui.RequestWarpsPressed -= RequestWarps;
Gui.ReturnToBodyPressed -= ReturnToBody;
Gui.GhostRolesPressed -= GhostRolesPressed;
Gui.VentCritterPressed -= OnVentCritterPressed; // DeltaV - freeform ghosties
Gui.VentCritterSelected -= OnVentCritterSelected; // DeltaV - freeform ghosties
Gui.TargetWindow.WarpClicked -= OnWarpClicked;
Gui.GhostRespawnPressed -= GuiOnGhostRespawnPressed; // Frontier
@ -195,4 +202,21 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
{
_system?.OpenGhostRoles();
}
// Begin DeltaV - freeform ghosties
private void OnVentCritterPressed()
{
_net.SendSystemNetworkMessage(new DVSpawnableGhostRoleCooldownRequestEvent());
}
private void OnVentCritterSelected(ProtoId<DVSpawnableGhostRolePrototype> role)
{
_net.SendSystemNetworkMessage(new DVSpawnableGhostRoleRequestEvent(role));
}
private void OnVentCritterCooldownUpdate(DVSpawnableGhostRoleCooldownUpdateEvent msg, EntitySessionEventArgs args)
{
Gui?.VentCritterWindow.SetCooldownEnd(msg.CooldownEnd);
}
// End DeltaV - freeform ghosties
}

View File

@ -5,6 +5,7 @@
<Button Name="ReturnToBodyButton" Text="{Loc ghost-gui-return-to-body-button}" />
<Button Name="GhostWarpButton" Text="{Loc ghost-gui-ghost-warp-button}" />
<Button Name="GhostRolesButton" />
<Button Name="VentCritterButton" Text="{Loc ghost-gui-spawn-vent-critter-button}" /> <!-- DeltaV: freeform ghosties -->
<Button Name="GhostRespawnButton" Text="{Loc ghost-gui-respawn}" /> <!-- Frontier -->
</BoxContainer>
</widgets:GhostGui>

View File

@ -1,8 +1,11 @@
using Content.Client._DV.Ghost; // DeltaV - freeform ghosties
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Ghost.Controls;
using Content.Shared._DV.Ghost.Roles; // DeltaV - freeform ghosties
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; // DeltaV - freeform ghosties
using Robust.Shared.Timing; // Frontier
using Content.Client._NF.UserInterface.Systems.Ghost.Controls; // Frontier
@ -17,10 +20,13 @@ public sealed partial class GhostGui : UIWidget
public GhostTargetWindow TargetWindow { get; }
public GhostRespawnRulesWindow RulesWindow { get; } // Frontier
public DVSpawnableGhostRoleWindow VentCritterWindow { get; } // DeltaV - freeform ghosties
public event Action? RequestWarpsPressed;
public event Action? ReturnToBodyPressed;
public event Action? GhostRolesPressed;
public event Action? VentCritterPressed; // DeltaV - freeform ghosties
public event Action<ProtoId<DVSpawnableGhostRolePrototype>>? VentCritterSelected; // DeltaV - freeform ghosties
public event Action? GhostRespawnPressed; // Frontier
private int _prevNumberRoles;
@ -31,6 +37,8 @@ public sealed partial class GhostGui : UIWidget
TargetWindow = new GhostTargetWindow();
RulesWindow = new GhostRespawnRulesWindow(); // Frontier
RulesWindow.RespawnButton.OnPressed += _ => GhostRespawnPressed?.Invoke(); // Frontier
VentCritterWindow = new DVSpawnableGhostRoleWindow(); // DeltaV - freeform ghosties
VentCritterWindow.RoleSelected += role => VentCritterSelected?.Invoke(role); // DeltaV - freeform ghosties
MouseFilter = MouseFilterMode.Ignore;
@ -38,12 +46,18 @@ public sealed partial class GhostGui : UIWidget
ReturnToBodyButton.OnPressed += _ => ReturnToBodyPressed?.Invoke();
GhostRolesButton.OnPressed += _ => GhostRolesPressed?.Invoke();
GhostRolesButton.OnPressed += _ => GhostRolesButton.StyleClasses.Remove(StyleClass.Negative);
VentCritterButton.OnPressed += _ =>
{
VentCritterPressed?.Invoke();
VentCritterWindow.OpenCentered();
}; // DeltaV - freeform ghosties
GhostRespawnButton.OnPressed += _ => RulesWindow.OpenCentered(); // Frontier
}
public void Hide()
{
TargetWindow.Close();
VentCritterWindow.Close(); // DeltaV - freeform ghosties
Visible = false;
}
@ -97,6 +111,7 @@ public sealed partial class GhostGui : UIWidget
if (disposing)
{
TargetWindow.Dispose();
VentCritterWindow.Dispose(); // DeltaV - freeform ghosties
}
}
}

View File

@ -0,0 +1,102 @@
using System.Linq;
using Content.Client.Silicons.Borgs;
using Content.Shared._CD.Silicons.Borgs;
using Content.Shared.Movement.Components;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client._CD.Silicons.Borgs;
/// <summary>
/// Primarily handles the appearance aspects of the borg subtype.
/// </summary>
public sealed class BorgSwitchableSubtypeSystem : SharedBorgSwitchableSubtypeSystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly BorgSystem _borg = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BorgSwitchableSubtypeComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<BorgSwitchableSubtypeComponent, AfterAutoHandleStateEvent>(OnAutoHandleEvent);
}
private void OnAutoHandleEvent(Entity<BorgSwitchableSubtypeComponent> ent, ref AfterAutoHandleStateEvent args)
{
SelectBorgSubtype(ent);
}
private void OnComponentStartup(Entity<BorgSwitchableSubtypeComponent> ent, ref ComponentStartup args)
{
SelectBorgSubtype(ent);
}
protected override void UpdateEntityAppearance(Entity<BorgSwitchableSubtypeComponent> entity, EntityPrototype borgSubtypePrototype)
{
// LOT of copy pasted code from BorgSwitchableTypeSystem, but is probably necessary unless the upstream code
// is refactored
if (!borgSubtypePrototype.TryGetComponent<BorgSubtypeDefinitionComponent>(out var borgSubtype, ComponentFactory))
return;
// get our required components
var (owner, _) = entity;
if (!TryComp<SpriteComponent>(entity, out var chassisSprite))
return;
// remove all existing layers
for (int i = chassisSprite.AllLayers.Count() - 1; i >= 0; i--)
{
_sprite.RemoveLayer((entity, chassisSprite), i);
}
for (int i = 0; i < borgSubtype.LayerData.Length; i++)
{
var layerData = borgSubtype.LayerData[i];
layerData.RsiPath = borgSubtype.SpritePath?.ToString();
if (borgSubtype.Offset != null)
layerData.Offset = borgSubtype.Offset;
_sprite.AddLayer((owner, chassisSprite), layerData, i);
}
if (TryComp<BorgChassisComponent>(entity, out var chassis))
{
_borg.SetMindStates(
(entity.Owner, chassis),
borgSubtype.SpriteHasMindState,
borgSubtype.SpriteNoMindState);
if (TryComp(entity, out AppearanceComponent? appearance))
{
// Queue update so state changes apply.
_appearance.QueueUpdate(entity, appearance);
}
}
if (borgSubtype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = borgSubtype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
base.UpdateEntityAppearance(entity, borgSubtypePrototype);
}
}

View File

@ -0,0 +1,23 @@
<Control xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Name="MainContainer"
Orientation="Vertical"
SeparationOverride="4"
Visible="False">
<controls:StripeBack>
<Label Text="{Loc 'cd-borg-select-subtype-flavour-text'}"
StyleClasses="LabelSubText"
HorizontalAlignment="Center"
Margin="0 12"/>
</controls:StripeBack>
<ScrollContainer Name="OptionsScrollContainer"
HScrollEnabled="True"
VScrollEnabled="False"
MinHeight="96">
<BoxContainer Name="OptionsContainer"
Orientation="Horizontal"
SeparationOverride="4"/>
</ScrollContainer>
</BoxContainer>
</Control>

View File

@ -0,0 +1,99 @@
using Content.Shared.Silicons.Borgs;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._CD.Silicons.Borgs.UI;
[GenerateTypedNameReferences]
public sealed partial class ChassisSpriteSelection : Control
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
public EntityPrototype? SubtypePrototype;
public event Action? SubtypeSelected;
private const int PrototypeViewSize = 2;
private static readonly ProtoId<EntityCategoryPrototype> _borgSubtypeCategory = "BorgSubtype";
public ChassisSpriteSelection()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void Update(BorgTypePrototype borgTypePrototype)
{
MainContainer.Visible = true;
OptionsContainer.RemoveAllChildren();
var buttonGroup = new ButtonGroup();
List<Button> buttons = new List<Button>();
buttons.Add(CreateDefaultSubtypeButton(borgTypePrototype, buttonGroup));
foreach (var ent in _prototype.Categories.GetValueRefOrNullRef(_borgSubtypeCategory))
{
if (!ent.TryGetComponent<Shared._CD.Silicons.Borgs.BorgSubtypeDefinitionComponent>(out var subtype, _componentFactory))
continue;
// Only add subtypes of the current selected 'main' borg type (engineering, medical, etc.)
if (subtype.ParentType != borgTypePrototype.ID)
continue;
var button = new Button
{
ToolTip = Loc.GetString($"al-borg-{borgTypePrototype.ID}-subtype-{ent.Name.Replace(' ', '-').ToLower()}-name"),
Group = buttonGroup,
MinHeight = 32,
};
button.OnPressed += _ =>
{
SubtypePrototype = ent;
SubtypeSelected?.Invoke();
};
button.AddChild(CreateEntityPrototypeView(subtype.DummyPrototype));
buttons.Add(button);
}
foreach (var button in buttons)
{
OptionsContainer.AddChild(button);
}
}
private Button CreateDefaultSubtypeButton(BorgTypePrototype borgTypePrototype, ButtonGroup group)
{
var button = new Button
{
ToolTip = "default",
Group = group,
MinHeight = 32,
};
button.OnPressed += _ =>
{
SubtypePrototype = null;
SubtypeSelected?.Invoke();
};
button.AddChild(CreateEntityPrototypeView(borgTypePrototype.DummyPrototype));
return button;
}
private EntityPrototypeView CreateEntityPrototypeView(EntProtoId entProtoId)
{
var entPrototypeView = new EntityPrototypeView();
entPrototypeView.SetPrototype(entProtoId);
entPrototypeView.Scale *= PrototypeViewSize;
return entPrototypeView;
}
}

View File

@ -0,0 +1,53 @@
using Content.Client.UserInterface.Controls;
using Content.Shared._DV.CosmicCult;
using Content.Shared._DV.CosmicCult.Prototypes;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.CosmicCult.UI;
[UsedImplicitly]
public sealed class CosmicGlyphDrawBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
private SimpleRadialMenu? _menu;
protected override void Open()
{
base.Open();
IoCManager.InjectDependencies(this);
_menu = this.CreateWindow<SimpleRadialMenu>();
_menu.OpenOverMouseScreenPosition();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not CosmicGlyphDrawBuiState glyphState || _menu == null)
return;
var options = new List<RadialMenuOptionBase>();
foreach (var glyphId in glyphState.Glyphs)
{
var glyph = _prototype.Index(glyphId);
options.Add(new RadialMenuActionOption<ProtoId<GlyphPrototype>>(SelectGlyph, glyph.ID)
{
IconSpecifier = RadialMenuIconSpecifier.With(glyph.Entity),
ToolTip = $"{Loc.GetString(glyph.Name)}\n{Loc.GetString(glyph.Tooltip)}",
});
}
_menu.SetButtons(options);
}
private void SelectGlyph(ProtoId<GlyphPrototype> glyphId)
{
SendMessage(new CosmicGlyphDrawSelectedMessage(glyphId));
}
}

View File

@ -19,9 +19,6 @@ public sealed class MonumentBoundUserInterface(EntityUid owner, Enum uiKey) : Bo
_menu = this.CreateWindow<MonumentMenu>();
_menu.OnSelectGlyphButtonPressed += protoId => SendMessage(new GlyphSelectedMessage(protoId));
_menu.OnRemoveGlyphButtonPressed += () => SendMessage(new GlyphRemovedMessage());
_menu.OnGainButtonPressed += OnInfluenceSelected;
}

View File

@ -5,13 +5,6 @@
MinSize="650 600"
SetSize="750 620">
<BoxContainer Orientation="Vertical" Margin="5 5 5 0">
<!-- Header -->
<BoxContainer Margin="7 10 7 0">
<!-- Progress bar -->
<ProgressBar Name="CultProgressBar" SetHeight="30" MaxValue="100" HorizontalExpand="True">
<Label Name="ProgressBarPercentage" HorizontalAlignment="Center"/>
</ProgressBar>
</BoxContainer>
<!-- Main body -->
<BoxContainer Orientation="Horizontal" VerticalExpand="True" Margin="10 20 10 10">
<!-- Left section -->
@ -31,27 +24,6 @@
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-available-label'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
<Label HorizontalAlignment="Center" Name="AvailableEntropy" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="0 5 0 0" HorizontalAlignment="Center">
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-next-stage-title'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
<Label HorizontalAlignment="Center" Name="EntropyUntilNextStage" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="0 5 0 0" HorizontalAlignment="Center">
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-seperator'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
<Label HorizontalAlignment="Center" Name="OrSeperator" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-crew-convert-title'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
<Label HorizontalAlignment="Center" Name="CrewToConvertUntilNextStage" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
</BoxContainer>
</BoxContainer>
<!-- Second section (Glyphs) -->
<BoxContainer Name="GlyphArea" HorizontalExpand="True" MinHeight="75" Orientation="Vertical" Margin="0 20 0 0">
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-glyphs-title'}" />
<PanelContainer StyleClasses="LowDivider" />
<!-- Filled out programatically -->
<GridContainer HorizontalAlignment="Center" Name="GlyphContainer" Columns="3" Margin="0 5" />
<Button Name="SelectGlyphButton" Text="{Loc 'monument-interface-glyphs-button-scribe'}" SetHeight="50" Margin="0 10 0 0" StyleClasses="ButtonColorPurpleAndCool" />
<Button Name="RemoveGlyphButton" Text="{Loc 'monument-interface-glyphs-button-unscribe'}" SetHeight="50" Margin="0 10 0 0" StyleClasses="ButtonColorPurpleAndCool" />
</BoxContainer>
</BoxContainer>
<!-- Right section -->

View File

@ -8,7 +8,6 @@ using Content.Shared._DV.CosmicCult.Prototypes;
using Content.Shared._DV.CosmicCult;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@ -32,10 +31,6 @@ public sealed partial class MonumentMenu : FancyWindow
// All influence prototypes
private readonly IEnumerable<InfluencePrototype> _influencePrototypes;
private readonly ButtonGroup _glyphButtonGroup;
private ProtoId<GlyphPrototype> _selectedGlyphProtoId = string.Empty;
private HashSet<ProtoId<GlyphPrototype>> _unlockedGlyphProtoIds = [];
public Action<ProtoId<GlyphPrototype>>? OnSelectGlyphButtonPressed;
public Action? OnRemoveGlyphButtonPressed;
public Action<ProtoId<InfluencePrototype>>? OnGainButtonPressed;
private int _entropyPerCultist = 0;
@ -52,9 +47,6 @@ public sealed partial class MonumentMenu : FancyWindow
_glyphButtonGroup = new ButtonGroup();
RemoveGlyphButton.OnPressed += _ => OnRemoveGlyphButtonPressed?.Invoke();
SelectGlyphButton.OnPressed += _ => OnSelectGlyphButtonPressed?.Invoke(_selectedGlyphProtoId);
_cfg.OnValueChanged(DCCVars.CosmicCultistEntropyValue, entropy =>
{
_entropyPerCultist = entropy;
@ -64,32 +56,10 @@ public sealed partial class MonumentMenu : FancyWindow
public void UpdateState(MonumentBuiState state)
{
_selectedGlyphProtoId = state.SelectedGlyph;
_unlockedGlyphProtoIds = state.UnlockedGlyphs;
CultProgressBar.BackgroundStyleBoxOverride = new StyleBoxFlat { BackgroundColor = new Color(15, 17, 30) };
CultProgressBar.ForegroundStyleBoxOverride = new StyleBoxFlat { BackgroundColor = new Color(91, 62, 124) };
UpdateBar(state);
UpdateEntropy(state);
UpdateGlyphs();
UpdateInfluences(state);
}
/// <summary>
/// Updates the progress bar
/// </summary>
private void UpdateBar(MonumentBuiState state)
{
var percentComplete = 100f * ((float)state.CurrentProgress / state.TargetProgress);
percentComplete = Math.Min(percentComplete, 100f);
CultProgressBar.Value = percentComplete;
ProgressBarPercentage.Text = Loc.GetString("monument-interface-progress-bar", ("percentage", percentComplete.ToString("0")));
}
/// <summary>
/// Updates the entropy fields
/// </summary>
@ -106,39 +76,6 @@ public sealed partial class MonumentMenu : FancyWindow
var crewToNextStage = (int)Math.Max(Math.Round((double)entropyToNextStage / _entropyPerCultist, MidpointRounding.ToPositiveInfinity), min); //force it to be at least one
AvailableEntropy.Text = Loc.GetString("monument-interface-entropy-value", ("infused", availableEntropy));
EntropyUntilNextStage.Text = Loc.GetString("monument-interface-entropy-value", ("infused", entropyToNextStage.ToString()));
CrewToConvertUntilNextStage.Text = crewToNextStage.ToString();
}
// Update all the glyph buttons
private void UpdateGlyphs()
{
GlyphContainer.RemoveAllChildren();
foreach (var glyph in _glyphPrototypes)
{
var boxContainer = new BoxContainer();
var unlocked = _unlockedGlyphProtoIds.Contains(glyph.ID);
var button = new Button
{
HorizontalExpand = true,
StyleClasses = { StyleClass.ButtonSquare },
ToolTip = Loc.GetString(glyph.Tooltip),
Group = _glyphButtonGroup,
Pressed = glyph.ID == _selectedGlyphProtoId,
Disabled = !unlocked,
Modulate = !unlocked ? Color.Gray : Color.White,
};
button.OnPressed += _ => _selectedGlyphProtoId = glyph.ID;
var glyphIcon = new TextureRect
{
Texture = _sprite.Frame0(glyph.Icon),
TextureScale = new Vector2(2f, 2f),
Stretch = TextureRect.StretchMode.KeepCentered,
};
button.AddChild(glyphIcon);
boxContainer.AddChild(button);
GlyphContainer.AddChild(boxContainer);
}
}
// Update all the influence thingies

View File

@ -111,6 +111,9 @@ public sealed class CrewMonitoringShuttleControl : BaseShuttleControl
if (blip.Blinks && !lit)
continue;
if (!blip.Coordinates.IsValid(EntManager))
continue;
var blipPos = _transform.ToMapCoordinates(blip.Coordinates);
if (mapPos.MapId == MapId.Nullspace)

View File

@ -0,0 +1,145 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Ghost.Controls.Roles;
using Content.Shared._DV.Ghost.Roles;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client._DV.Ghost;
public sealed class DVSpawnableGhostRoleWindow : FancyWindow
{
public event Action<ProtoId<DVSpawnableGhostRolePrototype>>? RoleSelected;
private GhostRoleRulesWindow? _windowRules;
private readonly BoxContainer _entries;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly List<Button> _spawnButtons = new();
private TimeSpan? _cooldownEnd;
public DVSpawnableGhostRoleWindow()
{
IoCManager.InjectDependencies(this);
Title = Loc.GetString("ghost-gui-spawn-vent-critter-window-title");
MinSize = new Vector2(490, 400);
SetSize = new Vector2(490, 500);
_entries = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
SeparationOverride = 8,
Margin = new Thickness(8),
};
var scroll = new ScrollContainer
{
HScrollEnabled = false,
Children =
{
_entries,
},
};
ContentsContainer.AddChild(scroll);
Populate();
OnClose += () => _windowRules?.Close();
}
private void Populate()
{
var roles = _prototype.EnumeratePrototypes<DVSpawnableGhostRolePrototype>()
.OrderBy(role => Loc.GetString(role.Name));
foreach (var role in roles)
{
var info = new GhostRoleInfoBox(Loc.GetString(role.Name), Loc.GetString(role.Description));
var button = new Button
{
Text = Loc.GetString("ghost-gui-spawn-vent-critter-spawn-button"),
VerticalAlignment = VAlignment.Center,
};
var id = new ProtoId<DVSpawnableGhostRolePrototype>(role.ID);
button.OnPressed += _ =>
{
if (IsCooldownActive())
return;
_windowRules?.Close();
_windowRules = new GhostRoleRulesWindow(Loc.GetString(role.Rules),
_ =>
{
RoleSelected?.Invoke(id);
Close();
});
_windowRules.OnClose += () =>
{
_windowRules = null;
};
_windowRules.OpenCentered();
};
_entries.AddChild(info);
_entries.AddChild(button);
_spawnButtons.Add(button);
}
if (_entries.ChildCount != 0)
return;
_entries.AddChild(new Label
{
Text = Loc.GetString("ghost-gui-spawn-vent-critter-none"),
});
}
public void SetCooldownEnd(TimeSpan? cooldownEnd)
{
_cooldownEnd = cooldownEnd;
UpdateCooldownButtons();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateCooldownButtons();
}
[MemberNotNullWhen(true, nameof(_cooldownEnd))]
private bool IsCooldownActive()
{
return _cooldownEnd > _timing.CurTime;
}
private void UpdateCooldownButtons()
{
if (IsCooldownActive())
{
var seconds = Math.Ceiling((_cooldownEnd.Value - _timing.CurTime).TotalSeconds);
foreach (var button in _spawnButtons)
{
button.Disabled = true;
button.Text = Loc.GetString("ghost-gui-spawn-vent-critter-spawn-cooldown", ("time", seconds));
}
return;
}
foreach (var button in _spawnButtons)
{
button.Disabled = false;
button.Text = Loc.GetString("ghost-gui-spawn-vent-critter-spawn-button");
}
}
}

View File

@ -1,43 +0,0 @@
using Content.Client.Stylesheets;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using static Content.Client.Stylesheets.StylesheetHelpers;
namespace Content.Client._DV.Stylesheets.Sheetlets;
[CommonSheetlet]
public sealed class SurgeryTextureButtonSheetlet <T> : Sheetlet<T> where T : PalettedStylesheet
{
private readonly Color _dollColor = Color.FromHex("#639bff");
public override StyleRule[] GetRules(T sheet, object config)
{
return
[
E<TextureButton>()
.Identifier("SurgeryTextureButton")
.PseudoHovered()
.Modulate(sheet.PositivePalette.HoveredElement),
E<TextureButton>()
.Identifier("SurgeryTextureButton")
.PseudoPressed()
.Modulate(sheet.PositivePalette.PressedElement),
E<TextureButton>()
.Identifier("SurgeryTextureButton")
.PseudoNormal()
.Modulate(_dollColor),
E<TextureButton>()
.Identifier("OpenIncision")
.PseudoHovered()
.Modulate(sheet.PositivePalette.HoveredElement),
E<TextureButton>()
.Identifier("OpenIncision")
.PseudoPressed()
.Modulate(sheet.PositivePalette.PressedElement),
E<TextureButton>()
.Identifier("OpenIncision")
.PseudoNormal()
.Modulate(sheet.HighlightPalette.HoveredElement),
];
}
}

View File

@ -10,6 +10,7 @@ using Content.Server.Mind;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.StationEvents.Components; // DeltaV
using Content.Shared.CCVar;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
@ -259,8 +260,12 @@ public sealed class NukeOpsTest : GameTest
// Delete the last nukie and make sure the round ends.
entMan.DeleteEntity(nukies[^1]);
Assert.That(roundEndSys.IsRoundEndRequested,
"All nukies were deleted, but the round didn't end!");
// BEGIN DeltaV - We convert to survival, so only check if the round ended if its ShuttleCall
if (rule.Component.RoundEndBehavior == RoundEndBehavior.ShuttleCall)
Assert.That(roundEndSys.IsRoundEndRequested, "All nukies were deleted, but the round didn't end!");
if (rule.Component.RoundEndBehavior == RoundEndBehavior.BecomeSurvival)
Assert.That(ticker.IsGameRuleAdded<RampingStationEventSchedulerComponent>(), "All nukies were deleted, but the round wasn't converted to survival.");
// END DeltaV
});
ticker.SetGamePreset((GamePresetPrototype?) null);

View File

@ -22,6 +22,7 @@ public abstract partial class InteractionTest
protected const string Wrench = "Wrench";
protected const string Screw = "Screwdriver";
protected const string Weld = "WelderExperimental";
protected const string Applicator = "NaniteApplicatorSyndicate"; // DeltaV
protected const string Pry = "Crowbar";
protected const string Cut = "Wirecutter";

View File

@ -186,7 +186,7 @@ public sealed class VendingInteractionTest : InteractionTest
Assert.That(IsUiOpen(VendingMachineUiKey.Key), Is.False, "Opened BUI of broken vending machine.");
// Repair the vending machine
await InteractUsing(Weld);
await InteractUsing(Applicator); // DeltaV
// Make sure the BUI can open now that the machine has been repaired
await Activate();

View File

@ -5,6 +5,7 @@ using Content.Shared.Damage.ForceSay;
using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Stunnable;
using Robust.Shared.Player;
@ -88,6 +89,9 @@ public sealed class DamageForceSaySystem : EntitySystem
if (!args.FellAsleep)
return;
if (Comp<MobStateComponent>(uid).CurrentState != MobState.Alive)
return;
TryForceSay(uid, component);
AllowNextSpeech(uid);
}

View File

@ -15,7 +15,7 @@ public sealed partial class NukeopsRuleComponent : Component
/// What will happen if all of the nuclear operatives will die. Used by LoneOpsSpawn event.
/// </summary>
[DataField]
public RoundEndBehavior RoundEndBehavior = RoundEndBehavior.ShuttleCall;
public RoundEndBehavior RoundEndBehavior = RoundEndBehavior.BecomeSurvival; // DeltaV - Change from ShuttlCall To BecomeSurvival
/// <summary>
/// Text for shuttle call if RoundEndBehavior is ShuttleCall.

View File

@ -1,3 +1,4 @@
using Content.Server.RoundEnd;
using Content.Shared.Roles;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@ -25,4 +26,14 @@ public sealed partial class ZombieRuleComponent : Component
/// </summary>
[DataField]
public float ZombieShuttleCallPercentage = 0.7f;
/// <summary>
/// DeltaV - The behavior of the round if all zombies are defeated.
/// </summary>
public RoundEndBehavior ZombieRoundEndBehavior = RoundEndBehavior.BecomeSurvival;
/// <summary>
/// DeltaV - The amount of time before the evac shuttle will arrive if the ZombieRoundEndBehavior is set to ShuttleCall.
/// </summary>
public TimeSpan ZombieShuttleDelay = TimeSpan.FromMinutes(10);
}

View File

@ -12,6 +12,7 @@ using Content.Server.Shuttles.Events;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components;
using Content.Server.Store.Systems;
using Content.Shared.Cuffs.Components; // DeltaV
using Content.Shared.GameTicking.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
@ -61,6 +62,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeOperativeComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<NukeOperativeComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<NukeOperativeComponent, EntityZombifiedEvent>(OnOperativeZombified);
SubscribeLocalEvent<NukeOperativeComponent, CuffedStateChangeEvent>(OnNukeOpCuffed); // DeltaV
SubscribeLocalEvent<NukeopsRoleComponent, GetBriefingEvent>(OnGetBriefing);
@ -318,6 +320,15 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
CheckRoundShouldEnd();
}
/// <summary>
/// DeltaV - Check if the round should end if a nuke op is cuffed. CheckRoundShouldEnd checks if theres any more
/// alive and non-cuffed nukies remaining.
/// </summary>
private void OnNukeOpCuffed(EntityUid uid, NukeOperativeComponent component, CuffedStateChangeEvent ev)
{
CheckRoundShouldEnd();
}
private void OnOperativeZombified(EntityUid uid, NukeOperativeComponent component, ref EntityZombifiedEvent args)
{
RemCompDeferred(uid, component);
@ -462,10 +473,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void CheckRoundShouldEnd()
{
var query = QueryActiveRules();
// BEGIN DeltaV - Allow round to become survival
// CheckRoundShouldEnd needs to be outside the query, because it might add an active rule
List<Entity<NukeopsRuleComponent>> nukeOpsRules = new();
while (query.MoveNext(out var uid, out _, out var nukeops, out _))
{
CheckRoundShouldEnd((uid, nukeops));
nukeOpsRules.Add((uid, nukeops));
}
nukeOpsRules.ForEach(CheckRoundShouldEnd);
// END DeltaV
}
private void CheckRoundShouldEnd(Entity<NukeopsRuleComponent> ent)
@ -500,12 +517,22 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// Check if there are nuke operatives still alive on the same map as the shuttle,
// or on the same map as the station.
// If there are, the round can continue.
var operatives = EntityQuery<NukeOperativeComponent, MobStateComponent, TransformComponent>(true);
// BEGIN DeltaV - Detect Nukie Failure Better
// We need to use a EntityQueryEnumerator instead of a EntityQuery, because we need the uid to check for cuffs
var operatives = new List<Entity<NukeOperativeComponent, MobStateComponent, TransformComponent>>();
var operativesEnumerator = EntityQueryEnumerator<NukeOperativeComponent, MobStateComponent, TransformComponent>();
while (operativesEnumerator.MoveNext(out var uid, out var nukeOp, out var mobState, out var transform))
{
operatives.Add((uid, nukeOp, mobState, transform));
}
var operativesAlive = operatives
.Where(op =>
op.Item3.MapID == shuttleMapId
|| op.Item3.MapID == targetStationMap)
.Any(op => op.Item2.CurrentState == MobState.Alive && op.Item1.Running);
.Where(op => op.Comp3.MapID == shuttleMapId
|| op.Comp3.MapID == targetStationMap)
.Any(op => op.Comp2.CurrentState == MobState.Alive && op.Comp1.Running &&
!TryComp<CuffableComponent>(op, out _)); // in the case the crew keeps one alive
// END DeltaV
if (operativesAlive)
return; // There are living operatives than can access the shuttle, or are still on the station's map.

View File

@ -3,7 +3,7 @@ using Content.Server.Chat.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Popups;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Server.RoundEnd; // DeltaV
using Content.Server.Station.Systems;
using Content.Server.Zombies;
using Content.Shared.GameTicking.Components;
@ -18,6 +18,7 @@ using Content.Shared.Zombies;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using System.Globalization;
using System.Linq; // DeltaV
namespace Content.Server.GameTicking.Rules;
@ -114,10 +115,32 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
/// </summary>
private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
{
// BEGIN DeltaV - Change mode to survival if zombies die
// Zombies have failed to launch and the mode has been switched to survival
if (zombieRuleComponent.ZombieRoundEndBehavior == RoundEndBehavior.Nothing)
return;
// END DeltaV
var healthy = GetHealthyHumans();
if (healthy.Count == 1) // Only one human left. spooky
_popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
// BEGIN DeltaV - Change mode to survival if zombies die
var anyInitialInfectedAlive = EntityQuery<InitialInfectedComponent, MobStateComponent>(false)
.Any(x => x.Item2.CurrentState != MobState.Dead);
var anyLivingZombies = EntityQuery<ZombieComponent, MobStateComponent>(false)
.Any(x => x.Item2.CurrentState != MobState.Dead);
var anyPendingZombies = EntityQuery<PendingZombieComponent>(false).Any();
// All II turned/dead, all entities that have turned are dead, and there are no more pending zombies
if (!anyInitialInfectedAlive && !anyLivingZombies && !anyPendingZombies)
{
_roundEnd.DoRoundEndBehavior(zombieRuleComponent.ZombieRoundEndBehavior, zombieRuleComponent.ZombieShuttleDelay);
zombieRuleComponent.ZombieRoundEndBehavior = RoundEndBehavior.Nothing; // stop this check in the future
}
// END DeltaV
if (GetInfectedFraction(false) > zombieRuleComponent.ZombieShuttleCallPercentage && !_roundEnd.IsRoundEndRequested())
{
foreach (var station in _station.GetStations())

View File

@ -27,7 +27,7 @@ using Content.Shared._DV.MedicalRecords;
namespace Content.Server.Medical;
public sealed class HealthAnalyzerSystem : EntitySystem
public sealed partial class HealthAnalyzerSystem : EntitySystem // DeltaV - Made Partial
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly PowerCellSystem _cell = default!;
@ -55,6 +55,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
subs.Event<HealthAnalyzerTriageClaimMessage>(OnHealthAnalyzerTriageClaimSelected);
});
// End DeltaV - Medical Records
InitializeReportPrinting(); // DeltaV - MedTek Reports
}
public override void Update(float frameTime)

View File

@ -61,7 +61,7 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
_audio.PlayPvs(comp.Sound, target);
_damageable.ChangeDamage(target, comp.StunDamage, origin: uid);
_stun.TryAddParalyzeDuration(target, comp.StunTime);
_stun.TryAddParalyzeDuration(target, comp.StunTime, true);
// short cooldown to prevent instant stunlocking
_useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);

View File

@ -1,3 +1,4 @@
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Research.Prototypes;
@ -12,6 +13,7 @@ public sealed class ReverseEngineeringSystem : SharedReverseEngineeringSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly NameModifierSystem _nameModifier = default!;
public override void Initialize()
{
@ -146,5 +148,6 @@ public sealed class ReverseEngineeringSystem : SharedReverseEngineeringSystem
disk.Recipes = new();
foreach (var id in recipes)
disk.Recipes.Add(id);
_nameModifier.RefreshNameModifiers(uid);
}
}

View File

@ -5,4 +5,11 @@ namespace Content.Server.StationEvents.Components;
/// Requires <c>AntagSelection</c>.
/// </summary>
[RegisterComponent]
public sealed partial class MidRoundAntagRuleComponent : Component;
public sealed partial class MidRoundAntagRuleComponent : Component
{
/// <summary>
/// When set to true, this spawner will prefer vent spawns over midround spawnpoints.
/// </summary>
[DataField]
public bool PreferVentSpawns;
}

View File

@ -23,7 +23,7 @@ public sealed class MidRoundAntagRule : StationEventSystem<MidRoundAntagRuleComp
if (!TryGetRandomStation(out var station))
return;
var spawns = FindSpawns(station.Value);
var spawns = FindSpawns(ent, station.Value);
if (spawns.Count == 0)
{
Log.Warning($"Couldn't find any suitable midround antag spawners for {ToPrettyString(ent):rule}");
@ -33,28 +33,48 @@ public sealed class MidRoundAntagRule : StationEventSystem<MidRoundAntagRuleComp
args.Coordinates.AddRange(spawns);
}
private List<MapCoordinates> FindSpawns(EntityUid station)
private void FindSpawnLocations(EntityUid station, List<MapCoordinates> spawns)
{
var spawns = new List<MapCoordinates>();
var query = EntityQueryEnumerator<MidRoundAntagSpawnLocationComponent, TransformComponent>();
while (query.MoveNext(out var uid, out _, out var xform))
{
if (StationSystem.GetOwningStation(uid, xform) == station && xform.GridUid != null)
spawns.Add(_xform.GetMapCoordinates(xform));
}
}
// if there are any midround antag spawns mapped, use them
if (spawns.Count > 0)
return spawns;
// otherwise, fall back to vent critter spawns
Log.Info($"Station {ToPrettyString(station):station} has no midround antag spawnpoints mapped, falling back. Please map them!");
private void FindVentLocations(EntityUid station, List<MapCoordinates> spawns)
{
var fallbackQuery = EntityQueryEnumerator<VentCritterSpawnLocationComponent, TransformComponent>();
while (fallbackQuery.MoveNext(out var uid, out _, out var xform))
{
if (StationSystem.GetOwningStation(uid, xform) == station && xform.GridUid != null)
spawns.Add(_xform.GetMapCoordinates(xform));
}
}
private List<MapCoordinates> FindSpawns(Entity<MidRoundAntagRuleComponent> rule, EntityUid station)
{
var spawns = new List<MapCoordinates>();
if (rule.Comp.PreferVentSpawns)
FindVentLocations(station, spawns);
else
FindSpawnLocations(station, spawns);
// if there are any midround antag spawns mapped, use them
if (spawns.Count > 0)
return spawns;
if (rule.Comp.PreferVentSpawns)
Log.Info($"Station {ToPrettyString(station):station} has no vent critter spawnpoints mapped, falling back. Please map them!");
else
Log.Info($"Station {ToPrettyString(station):station} has no midround antag spawnpoints mapped, falling back. Please map them!");
if (rule.Comp.PreferVentSpawns)
FindSpawnLocations(station, spawns);
else
FindVentLocations(station, spawns);
return spawns;
}

View File

@ -334,6 +334,11 @@ namespace Content.Server.RoundEnd
{
switch (behavior)
{
// BEGIN DeltaV - Convert Round To Survival
case RoundEndBehavior.BecomeSurvival:
_gameTicker.ConvertRoundToSurvival();
break;
// END DeltaV
case RoundEndBehavior.InstantEnd:
EndRound();
break;
@ -419,6 +424,11 @@ namespace Content.Server.RoundEnd
/// <summary>
/// Do nothing
/// </summary>
Nothing
Nothing,
/// <summary>
/// DeltaV - Replace scheduler. Crew should call for evac.
/// </summary>
BecomeSurvival,
}
}

View File

@ -18,7 +18,8 @@ public sealed class IonStormSystem : EntitySystem
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IonLawSystem _ionLaw = default!;
// [Dependency] private readonly IonLawSystem _ionLaw = default!; // DeltaV - ion laws
[Dependency] private readonly DVIonLawSystem _dvIonLaw = default!; // DeltaV - ion laws
/// <summary>
/// Randomly alters the laws of an individual silicon.
@ -72,7 +73,7 @@ public sealed class IonStormSystem : EntitySystem
}
// generate a new law...
var newLaw = _ionLaw.GetIonLaw();
var newLaw = _dvIonLaw.GenerateLaw(_robustRandom); // DeltaV - new ion laws // _ionLaw.GetIonLaw();
if (string.IsNullOrEmpty(newLaw))
return;

View File

@ -66,10 +66,7 @@ public sealed class EventManagerSystem : EntitySystem
/// </remarks>
public EntProtoId? TryGenerateRandomEvent(EntityTableSelector limitedEventsTable, TimeSpan? eventRunTime = null)
{
var availableEvents = AvailableEvents(eventRunTime: eventRunTime); // handles the player counts and individual event restrictions.
// Putting this here only makes any sense in the context of the toolshed commands in BasicStationEventScheduler. Kill me.
if (!TryBuildLimitedEvents(limitedEventsTable, out var limitedEvents))
if (!TryBuildLimitedEvents(limitedEventsTable, out var limitedEvents, currentTime: eventRunTime))
{
Log.Warning("Provided event table could not build dict!");
return null;

View File

@ -0,0 +1,43 @@
using Content.Shared._CD.Silicons.Borgs;
using Content.Shared.Inventory;
namespace Content.Server._CD.Silicons.Borgs;
/// <summary>
/// Server-side logic that shouldn't be exposed to the client.
/// </summary>
public sealed class BorgSwitchableSubstypeSystem : SharedBorgSwitchableSubtypeSystem
{
[Dependency] private readonly InventorySystem _inventorySystem = default!;
protected override void SelectBorgSubtype(Entity<BorgSwitchableSubtypeComponent> ent)
{
if (ent.Comp.BorgSubtype == null)
return;
if (!Prototypes.Index(ent.Comp.BorgSubtype.Value)
.TryGetComponent<BorgSubtypeDefinitionComponent>(out var borgSubtype, ComponentFactory))
return;
// Configure special components
if (Prototypes.TryIndex(ent.Comp.BorgSubtype, out var previousPrototype) &&
previousPrototype.TryGetComponent<BorgSubtypeDefinitionComponent>(out var previousSubtype, ComponentFactory))
{
if (previousSubtype.AddComponents is { } removeComponents)
EntityManager.RemoveComponents(ent, removeComponents);
}
if (borgSubtype.AddComponents is { } addComponents)
{
EntityManager.AddComponents(ent, addComponents);
}
// inventory template configuration (hats spacing)
if (TryComp(ent, out InventoryComponent? inventory))
{
_inventorySystem.SetTemplateId((ent.Owner, inventory), borgSubtype.InventoryTemplateId);
}
base.SelectBorgSubtype(ent);
}
}

View File

@ -5,7 +5,9 @@ using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
@ -16,22 +18,20 @@ public sealed class SynthSystem : EntitySystem
// Begin DeltaV - make strings static readonly
private static readonly ProtoId<TypingIndicatorPrototype> RobotTypingIndicator = "robot";
private static readonly ProtoId<ReagentPrototype> SynthBloodReagent = "SynthBlood";
private static readonly ProtoId<TagPrototype> SyntheticEmotesTag = "SyntheticEmotes";
private static readonly ProtoId<EmoteSoundsPrototype> SyntheticEmoteSounds = "SyntheticEmoteSounds";
private static readonly ProtoId<EmotePrototype>[] SiliconEmotes = ["Beep", "Chime", "Buzz", "Buzz-Two", "Ping"];
// End DeltaV
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ChatSystem _chat = default!;
private EmoteSoundsPrototype? _syntheticEmoteSounds; // delta-v - proper synthe emote implementation
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SynthComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<SynthComponent, EmoteEvent>(OnEmote); // delta-v - proper synthe emote implementation
SubscribeLocalEvent<SynthComponent, EmoteEvent>(OnEmote);
}
private void OnStartup(EntityUid uid, SynthComponent component, ComponentStartup args)
@ -42,26 +42,30 @@ public sealed class SynthSystem : EntitySystem
Dirty(uid, indicator);
}
// Begin DeltaV - Change blood amount according to original BloodstreamCompoent.ReferenceSolution volume
if (TryComp<BloodstreamComponent>(uid, out var bloodstream))
{
// Give them synth blood. Ion storm notif is handled in that system
_bloodstream.ChangeBloodReagents((uid, bloodstream), new([new(SynthBloodReagent, bloodstream.BloodReferenceSolution.Volume)]));
}
// End DeltaV
_tag.AddTag(uid, SyntheticEmotesTag); // delta-v - proper synthe emote implementation
// Add the silicon emotes to the allow list so we can ignore the emote whitelist/blacklist. This just allows the emote event to
// be sent but it does not associate a sound with the emote. We'll intercept that event in OnEmote and play the sound manually there
// since we cannot add just add sounds on the fly to the emote sounds of a species (everyone will have a different sound).
if (HasComp<VocalComponent>(uid) && TryComp<SpeechComponent>(uid, out var speech))
{
speech.AllowedEmotes.AddRange(SiliconEmotes);
Dirty(uid, speech);
}
}
// Start DeltaV - proper synthe emote implementation
private void OnEmote(EntityUid uid, SynthComponent component, ref EmoteEvent args)
{
if (args.Handled)
return;
_syntheticEmoteSounds ??= _proto.Index(SyntheticEmoteSounds);
args.Handled = _chat.TryPlayEmoteSound(uid, _syntheticEmoteSounds, args.Emote);
// If we make it this far, its an allowed emote, so just resolve the sound from
// the SyntheticEmoteSounds prototype and play it.
if (_proto.Resolve(SyntheticEmoteSounds, out var emoteSound))
args.Handled = _chat.TryPlayEmoteSound(uid, emoteSound, args.Emote);
}
// End DeltaV
}

View File

@ -5,6 +5,7 @@ using Content.Server.Players.PlayTimeTracking;
using Content.Server.Preferences.Managers;
using Content.Server.Station.Systems;
using Content.Shared.Administration;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Players;
using Content.Shared.Preferences;
@ -83,10 +84,11 @@ public sealed class SpawnCharacter : LocalizedEntityCommands
jobName = "Passenger"; // and if they fuck up, just default to passenger again
}
var gameTicker = _entitySys.GetEntitySystem<GameTicker>();
var coordinates = player.AttachedEntity != null
? _entityManager.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
: _entitySys.GetEntitySystem<GameTicker>().GetObserverSpawnPoint();
: gameTicker.GetObserverSpawnPoint();
if (player.AttachedEntity == null ||
!mindSystem.TryGetMind(player.AttachedEntity.Value, out var mindId, out var mind))
@ -95,6 +97,16 @@ public sealed class SpawnCharacter : LocalizedEntityCommands
var mobUid = _entityManager.System<StationSpawningSystem>().SpawnPlayerMob(coordinates, profile: character, entity: null, job: jobName, station: null);
mindSystem.TransferTo(mindId, mobUid);
var spawnCompleteEv = new PlayerSpawnCompleteEvent(mobUid,
player,
jobExists ? jobName : null,
lateJoin: false,
silent: true,
gameTicker.PlayersJoinedRoundNormally,
station: EntityUid.Invalid,
character);
_entityManager.EventBus.RaiseLocalEvent(mobUid, spawnCompleteEv, true);
shell.WriteLine(Loc.GetString("cmd-spawncharacter-complete"));
}

View File

@ -7,6 +7,7 @@ using Content.Server.Station.Systems;
using Content.Server.Traits;
using Content.Shared.Administration;
using Content.Shared.Roles;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Preferences;
using Robust.Server.Player;
@ -58,9 +59,11 @@ public sealed class SpawnPlayer : LocalizedEntityCommands
jobName = "Passenger"; // and if they fuck up, just default to passenger again
}
var gameTicker = _entitySys.GetEntitySystem<GameTicker>();
var coordinates = shell.Player != null && shell.Player.AttachedEntity != null
? _entityManager.GetComponent<TransformComponent>(shell.Player.AttachedEntity.Value).Coordinates
: _entitySys.GetEntitySystem<GameTicker>().GetObserverSpawnPoint();
: gameTicker.GetObserverSpawnPoint();
if (player.AttachedEntity == null ||
!mindSystem.TryGetMind(player.AttachedEntity.Value, out var mindId, out var mind))
@ -76,6 +79,16 @@ public sealed class SpawnPlayer : LocalizedEntityCommands
else
shell.WriteLine(Loc.GetString("cmd-spawnplayer-info-mind-not-transferred", ("entId", mobUid.ToString()), ("player", args[0])));
var spawnCompleteEv = new PlayerSpawnCompleteEvent(mobUid,
player,
jobExists ? jobName : null,
lateJoin: false,
silent: true,
gameTicker.PlayersJoinedRoundNormally,
station: EntityUid.Invalid,
character);
_entityManager.EventBus.RaiseLocalEvent(mobUid, spawnCompleteEv, true);
shell.WriteLine(Loc.GetString("cmd-spawnplayer-complete"));
}

View File

@ -18,7 +18,7 @@ public sealed partial class CosmicCultRuleComponent : Component
/// What happens if all of the cultists die.
/// </summary>
[DataField]
public RoundEndBehavior RoundEndBehavior = RoundEndBehavior.ShuttleCall;
public RoundEndBehavior RoundEndBehavior = RoundEndBehavior.BecomeSurvival;
/// <summary>
/// Sender for shuttle call.
@ -61,6 +61,45 @@ public sealed partial class CosmicCultRuleComponent : Component
[DataField]
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
#region Progress Checking
/// <summary>
/// The next time the monument should check the progress of the cult.
/// </summary>
[DataField]
public TimeSpan NextProgressCheck = TimeSpan.Zero;
/// <summary>
/// The amount of time between progress checks.
/// </summary>
[DataField]
public TimeSpan TimeBetweenProgressChecks = TimeSpan.FromMinutes(15);
/// <summary>
/// The progress value of the monument last time it was checked.
/// </summary>
[DataField]
public int LastProgress = 0;
/// <summary>
/// The number of times the the progress check as failed. Resets after some progress.
/// </summary>
[DataField]
public int ConsecutiveProgressFails = 0;
/// <summary>
/// The number of times the progress check has to fail for the round for round-end behavior to happen.
/// </summary>
[DataField]
public int ProgressFailureTolerance = 3;
[DataField]
public LocId ProgressFailTextShuttleCall = "cosmiccult-progress-fail-shuttle-call";
[DataField]
public LocId ProgressFailTextAnnouncement = "cosmiccult-progress-fail-announcement";
#endregion
[DataField]
public HashSet<EntityUid> Cultists = [];

View File

@ -63,11 +63,15 @@ using Robust.Shared.Timing;
using System.Collections.Immutable;
using System.Linq;
using Content.Shared.Body;
using Content.Shared.StatusIcon;
using Content.Shared.SSDIndicator;
namespace Content.Server._DV.CosmicCult;
/// <summary>
/// Where all the main stuff for Cosmic Cultists happens.
///
/// This should probably be broken up into a partial class.
/// </summary>
public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponent>
{
@ -172,6 +176,31 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
component.StewardVoteTimer = null;
StewardVote();
}
// Progress check. Is the cult trying to progress at all? Have they gotten to T3?
if (component.MonumentPlaced && component.CurrentTier < 3 && _timing.CurTime > component.NextProgressCheck)
{
// We're going to assume one monument.
var monument = EntityQuery<MonumentComponent>().First();
if (component.LastProgress == monument.CurrentProgress)
component.ConsecutiveProgressFails++;
else
component.ConsecutiveProgressFails = 0;
// They haven't progressed enough in X checks, convert to survival. They can still progress BUT
// if they haven't done ANYTHING in a long time, I doubt they will be a threat all of a sudden.
if (component.ConsecutiveProgressFails >= component.ProgressFailureTolerance)
{
SetWinType((uid, component), WinType.CrewMinor);
_roundEnd.DoRoundEndBehavior(component.RoundEndBehavior, component.EvacShuttleTime, component.RoundEndTextSender, component.ProgressFailTextShuttleCall, component.ProgressFailTextAnnouncement);
component.RoundEndBehavior = RoundEndBehavior.Nothing;
}
component.LastProgress = monument.CurrentProgress;
component.NextProgressCheck = _timing.CurTime + component.TimeBetweenProgressChecks;
}
if (component.ExtraRiftTimer is { } riftTimer && _timing.CurTime >= riftTimer && !component.RiftStop)
{
component.ExtraRiftTimer = _timing.CurTime + _rand.Next(TimeSpan.FromSeconds(230), TimeSpan.FromSeconds(360)); //3min50 to 6min between new rifts. Seconds instead of minutes for granularity.
@ -442,17 +471,19 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
}
}
private bool CultistsAlive()
private int GetCultistsAlive()
{
var query = EntityQueryEnumerator<CosmicCultComponent, MobStateComponent>();
int cultistsAlive = 0;
while (query.MoveNext(out var ent, out _, out var mobComp))
{
if (TryComp<CuffableComponent>(ent, out var cuffed) && cuffed.CuffedHandCount > 0) continue;
if (TryComp<SSDIndicatorComponent>(ent, out var ssd) && ssd.IsSSD) continue; // TODO: Maybe check how long.
if (mobComp.Running && mobComp.CurrentState != MobState.Dead)
return true;
cultistsAlive++;
}
return false;
return cultistsAlive;
}
private void OnMobStateChanged(Entity<CosmicCultComponent> ent, ref MobStateChangedEvent args)
@ -467,21 +498,26 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
private void CheckForActiveCultists()
{
if (CultistsAlive())
// A lone cultist can't do anything, so check for more than one.
if (GetCultistsAlive() > 1)
return;
var query = QueryActiveRules();
List<Entity<CosmicCultRuleComponent>> cosCultRules = new();
// ConfirmWinState needs to be outside the query, because it might add an active rule
while (query.MoveNext(out var ruleUid, out _, out var ruleComp, out _))
{
ConfirmWinState((ruleUid, ruleComp));
cosCultRules.Add((ruleUid, ruleComp));
}
cosCultRules.ForEach(ConfirmWinState);
}
private void ConfirmWinState(Entity<CosmicCultRuleComponent> ent)
{
var tier = ent.Comp.CurrentTier;
var LeaderAtCentcom = false;
var CultistsAtCentcom = 0;
var leaderAtCentcom = false;
var cultistsAtCentcom = 0;
var centcomm = _emergency.GetCentcommMaps();
var wrapup = AllEntityQuery<CosmicCultComponent, TransformComponent>();
while (wrapup.MoveNext(out var cultist, out _, out var cultistLocation))
@ -489,22 +525,22 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
if (cultistLocation.MapUid != null && centcomm.Contains(cultistLocation.MapUid.Value))
{
if (TryComp<CuffableComponent>(cultist, out var cuffed) && cuffed.CuffedHandCount > 0) continue; // If they are cuffed, they should be deconverted soon, so we don't count them.
CultistsAtCentcom++;
cultistsAtCentcom++;
if (HasComp<CosmicCultLeadComponent>(cultist))
LeaderAtCentcom = true;
leaderAtCentcom = true;
}
}
if (tier < 3)
SetWinType(ent, WinType.CrewMinor); //The monument didn't even reach tier 3, which means that either cult had a skill issue, or crew evacuated early. Minor win.
else if (LeaderAtCentcom) //If the monument reached tier 3, all cultists have glowing eyes now, so you shouldn't let them evacuate without cuffs on.
else if (leaderAtCentcom) //If the monument reached tier 3, all cultists have glowing eyes now, so you shouldn't let them evacuate without cuffs on.
SetWinType(ent, WinType.CultMajor); //The Monument wasn't completed, but the cult leader's alive and at Midpoint.
else if (CultistsAtCentcom >= 2)
else if (cultistsAtCentcom >= 2)
SetWinType(ent, WinType.CultMinor); //The Monument wasn't completed, but at least two cultists are alive and at Midpoint.
else
SetWinType(ent, WinType.Neutral); //The monument wasn't completed, no cultists escaped to midpoint. Some cultists still remain on the station, though.
if (CultistsAlive()) return; //If there are no cultists alive, ignore all previous checks, crew alreay won.
if (GetCultistsAlive() > 1) return; //If there are one or less cultists alive, ignore all previous checks, crew alreay won.
if (tier <= 1) //Prevent the cult getting cooked by accident before anyone even knows there's a cult.
{

View File

@ -1,14 +1,20 @@
using System.Linq;
using Content.Server.Popups;
using Content.Shared._DV.CosmicCult.Components.Examine;
using Content.Shared._DV.CosmicCult.Components;
using Content.Shared._DV.CosmicCult.Prototypes;
using Content.Shared._DV.CosmicCult;
using Content.Shared.Actions.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Interaction;
using Content.Shared.UserInterface;
using Content.Shared.Mobs.Systems;
using Content.Shared.Verbs;
using Robust.Server.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server._DV.CosmicCult;
@ -21,8 +27,11 @@ public sealed class CosmicGlyphSystem : SharedCosmicGlyphSystem
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly CosmicCultRuleSystem _cultRule = default!;
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly HashSet<Entity<CosmicCultComponent>> _cultists = [];
@ -32,9 +41,72 @@ public sealed class CosmicGlyphSystem : SharedCosmicGlyphSystem
{
SubscribeLocalEvent<CosmicGlyphComponent, ActivateInWorldEvent>(OnUseGlyph);
SubscribeLocalEvent<CosmicGlyphComponent, ComponentStartup>(OnGlyphCreated);
SubscribeLocalEvent<CosmicGlyphComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerbs);
SubscribeLocalEvent<CosmicCultComponent, EventCosmicDrawGlyph>(OnDrawGlyph);
SubscribeLocalEvent<CosmicGlyphDrawComponent, CosmicGlyphDrawSelectedMessage>(OnGlyphSelected);
base.Initialize();
}
private void OnGetAlternativeVerbs(Entity<CosmicGlyphComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess || !_cosmicCult.EntityIsCultist(args.User) || ent.Comp.State == GlyphStatus.Despawning)
return;
args.Verbs.Add(new AlternativeVerb
{
Text = Loc.GetString("cult-glyph-verb-erase"),
Act = () => EraseGlyph(ent),
});
}
private void OnDrawGlyph(Entity<CosmicCultComponent> ent, ref EventCosmicDrawGlyph args)
{
if (_cultRule.AssociatedGamerule(ent) is not { } cult ||
!TryComp<CosmicGlyphDrawComponent>(args.Action, out var drawComp))
return;
args.Handled = true;
var glyphs = GetAvailableGlyphs(cult.Comp.CurrentTier);
if (glyphs.Count == 0)
{
drawComp.Target = null;
_ui.CloseUi(args.Action.Owner, CosmicGlyphDrawUiKey.Key, args.Performer);
return;
}
drawComp.Target = args.Target;
_ui.OpenUi(args.Action.Owner, CosmicGlyphDrawUiKey.Key, args.Performer);
_ui.SetUiState(args.Action.Owner, CosmicGlyphDrawUiKey.Key, new CosmicGlyphDrawBuiState(glyphs));
}
private void OnGlyphSelected(Entity<CosmicGlyphDrawComponent> ent, ref CosmicGlyphDrawSelectedMessage args)
{
if (ent.Comp.Target is not { } target || !TryComp<ActionComponent>(ent, out var action) || action.Container != args.Actor
|| !TryComp<CosmicCultComponent>(args.Actor, out _) || _cultRule.AssociatedGamerule(args.Actor) is not { } cult
|| !_prototype.TryIndex(args.GlyphProtoId, out var glyph) || glyph.Tier > cult.Comp.CurrentTier)
{
ent.Comp.Target = null;
_ui.CloseUi(ent.Owner, CosmicGlyphDrawUiKey.Key, args.Actor);
return;
}
var spawned = Spawn(glyph.Entity, target);
_cultRule.TransferCultAssociation(args.Actor, spawned);
ent.Comp.Target = null;
_ui.CloseUi(ent.Owner, CosmicGlyphDrawUiKey.Key, args.Actor);
}
private List<ProtoId<GlyphPrototype>> GetAvailableGlyphs(int currentTier)
{
return _prototype.EnumeratePrototypes<GlyphPrototype>()
.Where(glyph => glyph.Tier <= currentTier)
.OrderBy(glyph => Loc.GetString(glyph.Name))
.Select(glyph => new ProtoId<GlyphPrototype>(glyph.ID))
.ToList();
}
#region Base trigger
private void OnGlyphCreated(Entity<CosmicGlyphComponent> ent, ref ComponentStartup args)

View File

@ -324,12 +324,6 @@ public sealed class MonumentSystem : SharedMonumentSystem
UpdateMonumentAppearance(uid, false);
//this is probably unnecessary but I have no idea where they get added to the list atm - ruddygreat
foreach (var glyphProto in _protoMan.EnumeratePrototypes<GlyphPrototype>().Where(proto => proto.Tier == 1))
{
uid.Comp.UnlockedGlyphs.Add(glyphProto.ID);
}
//basically completely unnecessary, but putting this here for sanity & futureproofing - ruddygreat
var query = EntityQueryEnumerator<CosmicCultComponent>();
while (query.MoveNext(out var cultist, out var cultComp))
@ -356,11 +350,6 @@ public sealed class MonumentSystem : SharedMonumentSystem
UpdateMonumentAppearance(uid, true);
foreach (var glyphProto in _protoMan.EnumeratePrototypes<GlyphPrototype>().Where(proto => proto.Tier == 2))
{
uid.Comp.UnlockedGlyphs.Add(glyphProto.ID);
}
var objectiveQuery = EntityQueryEnumerator<CosmicTierConditionComponent>();
while (objectiveQuery.MoveNext(out _, out var objectiveComp))
{
@ -398,11 +387,6 @@ public sealed class MonumentSystem : SharedMonumentSystem
if (_cosmicRule.AssociatedGamerule(uid) is not { } cult)
return;
foreach (var glyphProto in _protoMan.EnumeratePrototypes<GlyphPrototype>().Where(proto => proto.Tier == 3))
{
uid.Comp.UnlockedGlyphs.Add(glyphProto.ID);
}
UpdateMonumentAppearance(uid, true);
var objectiveQuery = EntityQueryEnumerator<CosmicTierConditionComponent>();

View File

@ -0,0 +1,56 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.GameTicking.Rules.Components;
using Content.Shared.Administration;
using Content.Shared.Database;
using Content.Shared.GameTicking.Components;
using Content.Shared.Prototypes;
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Localization;
using Content.Shared.Fax;
using Content.Server.StationEvents.Components;
namespace Content.Server.GameTicking;
/// <summary>
/// Extends upstream's <see cref="GameTicker" />.
/// </summary>
public sealed partial class GameTicker
{
private static readonly EntProtoId RampingSchedulerProto = "RampingStationEventScheduler";
private static readonly TimeSpan GracePeriod = TimeSpan.FromMinutes(10);
/// <summary>
/// DeltaV - Removes the basic scheduler and adds a ramping scheduler to the round. Does nothing
/// if there is already a ramping scheduler.
/// </summary>
/// <returns>True if the game rules either contain or added a RampingStationEventScheduler.</returns>
[PublicAPI]
public bool ConvertRoundToSurvival()
{
// Ramping scheduler is already added. Do nothing.
if (IsGameRuleActive(RampingSchedulerProto))
{
_chatManager.SendAdminAlert("RampingStationEventScheduler detected. No rules added.");
return true;
}
// Add a ramping scheduler with a delay.
var rampingScheduler = AddGameRule(RampingSchedulerProto);
var rampingSchedulerStart = _gameTiming.CurTime.Add(GracePeriod);
EnsureComp<DelayedStartRuleComponent>(rampingScheduler).RuleStartTime = rampingSchedulerStart;
_chatManager.SendAdminAlert($"Major antag defeated. Converting to survival at {rampingSchedulerStart}.");
// End Basic Rules
var basicRules = EntityQueryEnumerator<BasicStationEventSchedulerComponent>();
while (basicRules.MoveNext(out var uid, out var rule))
EndGameRule(uid);
return IsGameRuleActive(RampingSchedulerProto);
}
}

View File

@ -0,0 +1,184 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Administration.Logs;
using Content.Server.Mind;
using Content.Server.Roles;
using Content.Server.StationEvents.Components;
using Content.Shared._DV.CCVars;
using Content.Shared._DV.Ghost.Roles;
using Content.Shared.Database;
using Content.Shared.Ghost;
using Content.Shared.Mind.Components;
using Content.Shared.Players;
using Content.Shared.Popups;
using Content.Shared.Station.Components;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server._DV.Ghost.Roles;
public sealed class DVSpawnableGhostRoleSystem : EntitySystem
{
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
private readonly List<EntityCoordinates> _stationVents = new();
private readonly List<EntityCoordinates> _allVents = new();
private readonly Dictionary<NetUserId, TimeSpan> _cooldowns = new();
private TimeSpan _spawnCooldown;
public override void Initialize()
{
base.Initialize();
_player.PlayerStatusChanged += OnPlayerStatusChanged;
Subs.CVar(_cfg, DCCVars.VentCritterGhostRoleSpawnCooldown, value => _spawnCooldown = value < TimeSpan.Zero ? TimeSpan.Zero : value, true);
SubscribeNetworkEvent<DVSpawnableGhostRoleRequestEvent>(OnSpawnRequest);
SubscribeNetworkEvent<DVSpawnableGhostRoleCooldownRequestEvent>(OnCooldownRequest);
}
public override void Shutdown()
{
base.Shutdown();
_player.PlayerStatusChanged -= OnPlayerStatusChanged;
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs evt)
{
if (evt.NewStatus == SessionStatus.Connected)
{
SendCooldownUpdate(evt.Session);
}
}
private void OnSpawnRequest(DVSpawnableGhostRoleRequestEvent msg, EntitySessionEventArgs args)
{
var session = args.SenderSession;
if (session.AttachedEntity is not { Valid: true } attached
|| !HasComp<GhostComponent>(attached))
{
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{session:player} sent {nameof(DVSpawnableGhostRoleRequestEvent)} without being a ghost.");
return;
}
if (TestCooldown(session.UserId, out var cooldownEnd))
{
SendCooldownUpdate(session);
var remaining = Math.Ceiling((cooldownEnd.Value - _timing.CurTime).TotalSeconds);
_popup.PopupEntity(Loc.GetString("ghost-gui-spawn-vent-critter-cooldown-popup", ("time", remaining)), attached, attached);
return;
}
if (!_prototype.TryIndex(msg.Prototype, out var role)
|| !_prototype.TryIndex<EntityPrototype>(role.Entity, out _))
{
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{session:player} tried to spawn as invalid {nameof(DVSpawnableGhostRoleRequestEvent)} prototype {msg.Prototype}.");
return;
}
if (!TryPickVent(out var coords))
{
_popup.PopupEntity(Loc.GetString("ghost-gui-spawn-vent-critter-no-vents"), attached, attached);
return;
}
var mob = SpawnAtPosition(role.Entity, coords);
_transform.AttachToGridOrMap(mob);
EnsureComp<MindContainerComponent>(mob);
DebugTools.AssertNotNull(session.ContentData());
if(_mind.TryGetMind(session.UserId, out _, out var mind) && !mind.IsVisitingEntity)
_mind.WipeMind(session);
var newMind = _mind.CreateMind(session.UserId, Comp<MetaDataComponent>(mob).EntityName);
_mind.SetUserId(newMind, session.UserId);
_mind.TransferTo(newMind, mob);
_role.MindAddRoles(newMind, role.MindRoles, newMind);
_cooldowns[session.UserId] = _timing.CurTime + _spawnCooldown;
SendCooldownUpdate(session);
_adminLog.Add(LogType.Action, LogImpact.Low, $"{session:player} spawned as a vent critter {msg.Prototype}");
}
private void OnCooldownRequest(DVSpawnableGhostRoleCooldownRequestEvent msg, EntitySessionEventArgs args)
{
SendCooldownUpdate(args.SenderSession);
}
/// <param name="userId">The user to test for</param>
/// <param name="cooldownEndsAt">When the cooldown will end</param>
/// <returns>If the user is on cooldown</returns>
private bool TestCooldown(NetUserId userId, [NotNullWhen(true)] out TimeSpan? cooldownEndsAt)
{
if (_cooldowns.TryGetValue(userId, out var cooldownEnd))
{
cooldownEndsAt = cooldownEnd;
if (_timing.CurTime < cooldownEnd)
return true;
_cooldowns.Remove(userId);
}
cooldownEndsAt = null;
return false;
}
private void SendCooldownUpdate(ICommonSession session)
{
TestCooldown(session.UserId, out var cooldownEnd);
RaiseNetworkEvent(new DVSpawnableGhostRoleCooldownUpdateEvent(cooldownEnd), session.Channel);
}
private bool TryPickVent(out EntityCoordinates coords)
{
_stationVents.Clear();
_allVents.Clear();
var query = EntityQueryEnumerator<VentCritterSpawnLocationComponent, TransformComponent>();
while (query.MoveNext(out _, out _, out var transform))
{
if (!transform.Anchored || !transform.Coordinates.IsValid(EntityManager))
continue;
_allVents.Add(transform.Coordinates);
if (transform.GridUid is { } grid && HasComp<StationMemberComponent>(grid))
_stationVents.Add(transform.Coordinates);
}
if (_stationVents.Count > 0)
{
coords = _random.Pick(_stationVents);
return true;
}
if (_allVents.Count > 0)
{
coords = _random.Pick(_allVents);
return true;
}
coords = default;
return false;
}
}

View File

@ -11,12 +11,12 @@ public sealed class IonLawCommand : ToolshedCommand
{
[Dependency] private readonly IRobustRandom _random = default!;
private IonLawSystem? _ionLaw;
private DVIonLawSystem? _ionLaw;
[CommandImplementation("generate")]
public IEnumerable<string> GenerateNames(int count)
{
_ionLaw ??= GetSys<IonLawSystem>();
_ionLaw ??= GetSys<DVIonLawSystem>();
for (var i = 0; i < count; i++)
{

View File

@ -0,0 +1,35 @@
using Content.Shared.Paper;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Medical.Components;
/// <summary>
/// Extends upstream's HealthAnalyzerComponent.
/// </summary>
/// </remarks>
public sealed partial class HealthAnalyzerComponent : Component
{
/// <summary>
/// Time when the component can print again
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan PrintAllowedAfter = TimeSpan.Zero;
/// <summary>
/// Cooldown between individual prints to prevent entity and noise spam.
/// </summary>
[DataField]
public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
/// <summary>
/// Prototype of an entity to use as a template for printing. The paper may contain placeholders (wrapped in braces)
/// which will be filled in during printing.
/// </summary>
[DataField]
public EntProtoId<PaperComponent> PrintTemplate = "PaperWrittenMedicalMedtekPatientRecord";
[DataField]
public SoundSpecifier PrintReportSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
}

View File

@ -0,0 +1,181 @@
using System.Linq;
using System.Text.RegularExpressions;
using Content.Server.Hands.Systems;
using Content.Server.Medical.Components;
using Content.Shared._DV.Medical;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Labels.EntitySystems;
using Content.Shared.Paper;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Medical;
public sealed partial class HealthAnalyzerSystem : EntitySystem
{
private static readonly Regex TemplateInsert = new(@"\{([\w.]+)\}", RegexOptions.Compiled);
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly LabelSystem _label = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly PaperSystem _paper = default!;
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
public void InitializeReportPrinting()
{
SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerPrintReportMessage>(OnPrint);
}
private void OnPrint(Entity<HealthAnalyzerComponent> entity, ref HealthAnalyzerPrintReportMessage args)
{
var printer = entity.Comp;
// Prevent users from printing too quickly
if (printer.PrintAllowedAfter >= _timing.CurTime)
return;
HealthAnalyzerComponent? analyzer = null;
if (!Resolve(entity.Owner, ref analyzer))
return;
// The health analyzer UI disables the button when the patient is invalid or out of range
if (analyzer.ScannedEntity is not { Valid: true } patient)
return;
var user = args.Actor;
if (!IsInRange(patient, user, analyzer.MaxScanRange))
return;
// Create slip of paper according to template
var paper = Spawn(printer.PrintTemplate, Transform(user).Coordinates);
ComposePatientRecord(paper, user, patient);
_label.Label(paper, GetEntityName(patient));
_hands.PickupOrDrop(user, paper);
_audio.PlayPvs(entity.Comp.PrintReportSound, user);
// Start cooldown
printer.PrintAllowedAfter = _timing.CurTime + printer.PrintCooldown;
}
private bool IsInRange(EntityUid patient, EntityUid user, float? maxScanRange)
{
if (maxScanRange == null)
{
return true;
}
return _transformSystem.InRange(
(patient, Transform(patient)),
(user, Transform(user)),
maxScanRange.Value
);
}
private void ComposePatientRecord(EntityUid uid, EntityUid responder, EntityUid patient)
{
PaperComponent? paper = null;
DamageableComponent? damageable = null;
if (!Resolve(uid, ref paper) || !Resolve(patient, ref damageable))
{
return;
}
var template = paper.Content;
// Anything in this dictionary can be interpolated into the print template
Dictionary<string, Func<string>> inserts = new()
{
{ "patient.name", () => GetEntityName(patient) },
{ "patient.species", () => GetEntitySpecies(patient) },
{ "responder.name", () => GetEntityName(responder) },
{ "roundTime", () => (_timing.CurTime - _gameTicker.RoundStartTimeSpan).ToString(@"hh\:mm") },
{ "damageList", () => ComposeDamageList((patient, damageable)) },
};
var content = TemplateInsert.Replace(template,
match =>
{
var key = match.Groups[1].Value;
if (inserts.TryGetValue(key, out var value))
{
return value.Invoke();
}
return match.Value;
});
_paper.SetContent((uid, paper), content);
}
private string ComposeDamageList(Entity<DamageableComponent> ent)
{
var damages = _damageable.GetPositiveDamage(ent);
if (damages.GetTotal() <= 0)
{
return Loc.GetString("health-analyzer-printout-damage-none");
}
var report = new FormattedMessage();
var groupDamages = damages.GetDamagePerGroup(_prototypes);
var groups = groupDamages.OrderByDescending(entry => entry.Value);
var damage = damages.DamageDict;
foreach (var (groupId, groupDamage) in groups)
{
if (groupDamage <= 0)
{
continue;
}
var group = _prototypes.Index(groupId);
// Group header
var groupTitleText = Loc.GetString(
"health-analyzer-printout-damage-group-text",
("damageGroup", group.LocalizedName),
("amount", groupDamage)
);
report.AddMarkupPermissive(groupTitleText);
report.PushNewline();
// List individual damage types
foreach (var type in group.DamageTypes)
{
var amount = damage.GetValueOrDefault(type, 0);
if (amount <= 0)
{
continue;
}
report.AddMarkupPermissive(Loc.GetString(
"health-analyzer-printout-damage-type-text",
("damageType", _prototypes.Index(type).LocalizedName),
("amount", amount)
));
report.PushNewline();
}
}
return report.ToMarkup();
}
private string GetEntityName(EntityUid uid)
{
return HasComp<MetaDataComponent>(uid)
? Identity.Name(uid, EntityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-text");
}
private string GetEntitySpecies(EntityUid uid)
{
return Loc.GetString(
TryComp<HumanoidProfileComponent>(uid, out var appearance)
? _prototypes.Index(appearance.Species).Name
: "health-analyzer-window-entity-unknown-species-text"
);
}
}

View File

@ -1,4 +1,6 @@
using System.Linq;
using System.Numerics;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Server.NodeContainer.EntitySystems;
@ -6,11 +8,19 @@ using Content.Server.NodeContainer.Nodes;
using Content.Shared._DV.NodeCrawl;
using Content.Shared.Atmos;
using Content.Shared.NodeContainer;
using Content.Shared.Zombies;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Content.Server._DV.NodeCrawl;
public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly BarotraumaSystem _barotrauma = default!;
[Dependency] private readonly IReflectionManager _reflection = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize()
{
base.Initialize();
@ -20,11 +30,99 @@ public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
SubscribeLocalEvent<NodeCrawlerComponent, InhaleLocationEvent>(OnInhaleLocation);
SubscribeLocalEvent<NodeCrawlerComponent, ExhaleLocationEvent>(OnExhaleLocation);
SubscribeLocalEvent<NodeCrawlerComponent, AtmosExposedGetAirEvent>(OnGetAir);
SubscribeLocalEvent<NodeCrawlerComponent, EntityZombifiedEvent>(OnZombify);
}
private GasMixture? GetExistingAir(Entity<NodeCrawlerMovementComponent> movement)
{
if (movement.Comp.Node is not { } node)
return null;
if (!TryComp<NodeContainerComponent>(node, out var nodeContainer))
return null;
foreach (var containedNode in nodeContainer.Nodes.Values)
{
if (containedNode is not PipeNode pipe)
continue;
return pipe.Air;
}
return null;
}
protected override void SetupAir(Entity<NodeCrawlerMovementComponent> movement)
{
base.SetupAir(movement);
if (movement.Comp.HeldCrawler is not { } heldCrawler ||
!TryComp<BarotraumaComponent>(heldCrawler, out var barotrauma))
{
return;
}
if (GetExistingAir(movement) is { } existingAir)
{
var pressure = existingAir.Pressure switch
{
// Adjust pressure based on equipment. Works differently depending on if it's "high" or "low".
<= Atmospherics.WarningLowPressure => _barotrauma.GetFeltLowPressure(heldCrawler, barotrauma, existingAir.Pressure),
>= Atmospherics.WarningHighPressure => _barotrauma.GetFeltHighPressure(heldCrawler, barotrauma, existingAir.Pressure),
_ => existingAir.Pressure,
};
if (pressure is >= Atmospherics.HazardLowPressure and <= Atmospherics.HazardHighPressure)
return;
}
var xform = Transform(movement);
var indices = _transform.GetGridTilePositionOrDefault((movement, xform));
if (_atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, indices, true) is { Temperature: > 0f } environment)
{
// we want to get one atmosphere's worth of pressure in the air volume of the component
// we need to take an amount of moles from the gas, so
// PV = nRT
// (Atmospherics.OneAtmosphere) * (movement.Comp.AirVolume) = (amount of mols) * R * (environment.Temperature)
// solve for amount of mols
// amount of mols = (Atmospherics.OneAtmosphere) * (movement.Comp.AirVolume) / R * (environment.Temperature)
var transferMoles = Atmospherics.OneAtmosphere * movement.Comp.AirVolume / (environment.Temperature * Atmospherics.R);
movement.Comp.Air = new(movement.Comp.AirVolume);
_atmosphere.Merge(movement.Comp.Air, environment.Remove(transferMoles));
}
}
protected override void EjectAir(Entity<NodeCrawlerMovementComponent> movement)
{
base.EjectAir(movement);
if (movement.Comp.Air is not { } air)
return;
var xform = Transform(movement);
var indices = _transform.GetGridTilePositionOrDefault((movement, xform));
if (_atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, indices, true) is not { } environment)
return;
_atmosphere.Merge(environment, air);
air.Clear();
}
private Entity<NodeCrawlerMovementComponent>? GetMovement(Entity<NodeCrawlerComponent> crawler)
{
if (!TryComp<NodeCrawlerMovementComponent>(crawler.Comp.Mover, out var mover))
return null;
return new(crawler.Comp.Mover.Value, mover);
}
private Entity<NodeContainerComponent>? GetNodeContainer(Entity<NodeCrawlerComponent> crawler)
{
if (!TryComp<NodeCrawlerMovementComponent>(crawler.Comp.Mover, out var mover) || mover.Node is not { } node)
if (GetMovement(crawler) is not { } mover || mover.Comp.Node is not { } node)
return null;
if (!TryComp<NodeContainerComponent>(node, out var nodeContainer))
@ -35,6 +133,9 @@ public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
private GasMixture? GetAir(Entity<NodeCrawlerComponent> crawler)
{
if (GetMovement(crawler)?.Comp.Air is { } air)
return air;
if (GetNodeContainer(crawler) is not { } nodeContainer)
return null;
@ -79,12 +180,23 @@ public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
return;
// ugly workaround for https://github.com/space-wizards/RobustToolbox/issues/6694 not letting List<Type>
// get serialized properly
var possibleTypes = ent.Comp.ReachableNodeTypes.Select(it => _reflection.GetType(it)).ToList();
ent.Comp.DeadEnd = false;
var set = new HashSet<EntityUid>();
foreach (var node in nodeContainer.Nodes.Values)
{
foreach (var reachable in node.ReachableNodes)
{
if (possibleTypes.Count != 0 &&
!possibleTypes.TrueForAll(type => reachable.GetType() == type))
{
continue;
}
DebugTools.Assert(HasComp<CrawlableNodeComponent>(reachable.Owner), $"Node {ToPrettyString(reachable.Owner)} reachable from {ToPrettyString(ent)} should be a crawlable node, but wasn't");
set.Add(reachable.Owner);
}
@ -98,4 +210,16 @@ public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
ent.Comp.ReachableNodes = set;
Dirty(ent);
}
/// <summary>
/// Lengthens the amount of time that it takes a zombified vent crawling mob to get back into the vent.
/// They are really hard to kill otherwise.
/// </summary>
/// <param name="ent"></param>
/// <param name="args"></param>
private void OnZombify(Entity<NodeCrawlerComponent> ent, ref EntityZombifiedEvent args)
{
ent.Comp.EnterDelay = ent.Comp.ZombieEnterDelay;
Dirty(ent);
}
}

View File

@ -0,0 +1,44 @@
using Content.Shared.Store;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.Store.Conditions;
/// <summary>
/// XOR (eXclusive-OR) Buy Condition
/// A condition where that blocks buying a listing if another listing
/// specified is bought before it. Used to enforce that a traitor
/// can only buy 1 of X options.
///
/// Primarily used for choose-your-own-adventure DAGD Implants.
/// </summary>
public sealed partial class XORBuyCondition : ListingCondition
{
/// <summary>
/// Listing(s) that if bought, block this purchase, if any.
/// </summary>
[DataField(required: true)]
public HashSet<ProtoId<ListingPrototype>>? Listings;
public override bool Condition(ListingConditionArgs args)
{
if (!args.EntityManager.TryGetComponent<StoreComponent>(args.StoreEntity, out var storeComp))
return false;
var allListings = storeComp.FullListingsCatalog;
if (Listings != null)
{
foreach (var blacklistListing in Listings)
{
foreach (var listing in allListings)
{
if (listing.ID == blacklistListing.Id && listing.PurchaseAmount > 0)
return false; // it was purchased, so return false to fail the condition
}
}
}
return true; // If no purchases were found, return true
}
}

View File

@ -0,0 +1,53 @@
using Content.Server.GameTicking;
using Content.Shared._Goobstation.StationReport;
using Content.Shared.Paper;
using Robust.Shared.GameObjects;
using System.Text;
namespace Content.Server._Goobstation.StationReportSystem;
public sealed class StationReportSystem : EntitySystem
{
//this is shitcode?
public override void Initialize()
{
//subscribes to the endroundevent
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextAppend);
}
private void OnRoundEndTextAppend(RoundEndTextAppendEvent args)
{
//locates the first entity with StationReportComponent and combines them
var stationReports = new StringBuilder();
var reportNumber = 1;
var query = EntityQueryEnumerator<StationReportComponent>();
while (query.MoveNext(out var uid, out _)) // finds every entity with StationReportComponent
{
if (!TryComp<PaperComponent>(uid, out var paper))
continue;
if (string.IsNullOrWhiteSpace(paper.Content))
continue;
stationReports.AppendLine($"[bold]Station Report #{reportNumber}[/bold]");
stationReports.AppendLine(paper.Content);
stationReports.AppendLine();
reportNumber++;
}
var stationReportText = stationReports.Length > 0
? stationReports.ToString()
: null;
BroadcastStationReport(stationReportText);
}
//sends a networkevent to tell the client to update the stationreporttext when recived
public void BroadcastStationReport(string? stationReportText)
{
RaiseNetworkEvent(new StationReportEvent(stationReportText));//to send to client
RaiseLocalEvent(new StationReportEvent(stationReportText));//to send to discord intergration
}
}

View File

@ -13,7 +13,7 @@ public sealed partial class RevenantStasisComponent : Component
public Entity<RevenantComponent> Revenant;
[ViewVariables(VVAccess.ReadOnly)]
public TimeSpan StasisDuration = TimeSpan.FromSeconds(60);
public TimeSpan StasisDuration = TimeSpan.FromSeconds(100); // DeltaV - change from 60 to 100s
public RevenantStasisComponent(TimeSpan stasisDuration, Entity<RevenantComponent> revenant)
{

View File

@ -0,0 +1,7 @@
namespace Content.Server.Speech.Components;
/// <summary>
/// Scraw!!
/// </summary>
[RegisterComponent]
public sealed partial class AllulaloAccentComponent : Component { }

View File

@ -0,0 +1,52 @@
using System.Text.RegularExpressions;
using Content.Server.Speech.Components;
using Content.Shared.Speech;
namespace Content.Server._Impstation.Speech.EntitySystems;
public sealed class AllulaloAccentSystem : EntitySystem
{
private static readonly Regex RegexLowerslurR = new Regex("r{1,3}");
private static readonly Regex RegexUpperslurR = new Regex("R{1,3}");
private static readonly Regex RegexLowerdoubleCK = new Regex("ck{1,3}");
private static readonly Regex RegexUpperdoubleCK = new Regex("CK{1,3}");
private static readonly Regex RegexCasedoubleCK = new Regex("Ck{1,3}");
private static readonly Regex RegexCameldoubleCK = new Regex("cK{1,3}");
private static readonly Regex RegexLowerdoubleCKT = new Regex("ct{1,3}");
private static readonly Regex RegexUpperdoubleCKT = new Regex("CT{1,3}");
private static readonly Regex RegexCasedoubleCKT = new Regex("Ct{1,3}");
private static readonly Regex RegexCameldoubleCKT = new Regex("cT{1,3}");
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AllulaloAccentComponent, AccentGetEvent>(OnAccent);
}
private void OnAccent(EntityUid uid, AllulaloAccentComponent component, AccentGetEvent args)
{
var message = args.Message;
// Im not writing music in this one
message = RegexLowerslurR.Replace(message, "rr");
// Sorry to the snalienaccentsystem fans
message = RegexUpperslurR.Replace(message, "RR");
//
message = RegexLowerdoubleCK.Replace(message, "ck-ck");
//
message = RegexUpperdoubleCK.Replace(message, "CK-CK");
//
message = RegexCasedoubleCK.Replace(message, "Ck-ck");
//
message = RegexCameldoubleCK.Replace(message, "cK-CK");
//
message = RegexLowerdoubleCKT.Replace(message, "ck-ct");
//
message = RegexUpperdoubleCKT.Replace(message, "CK-CT");
//
message = RegexCasedoubleCKT.Replace(message, "Ck-ct");
//
message = RegexCameldoubleCKT.Replace(message, "cK-CT");
args.Message = message;
}
}

View File

@ -73,6 +73,7 @@ public sealed partial class SleepingSystem : EntitySystem
SubscribeLocalEvent<SleepingComponent, StunEndAttemptEvent>(OnStunEndAttempt);
SubscribeLocalEvent<SleepingComponent, StandUpAttemptEvent>(OnStandUpAttempt);
SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectRelayedEvent<MobStateChangedEvent>>(OnStatusMobStateChanged);
SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
//SubscribeLocalEvent<SleepingComponent, EmoteAttemptEvent>(OnEmoteAttempt); # DeltaV - Reverted "Block emotes for sleeping" (upstream PR #32779)
@ -163,7 +164,6 @@ public sealed partial class SleepingSystem : EntitySystem
private void OnSpeakAttempt(Entity<SleepingComponent> ent, ref SpeakAttemptEvent args)
{
// TODO reduce duplication of this behavior with MobStateSystem somehow
if (HasComp<AllowNextCritSpeechComponent>(ent))
{
RemCompDeferred<AllowNextCritSpeechComponent>(ent);
@ -270,14 +270,19 @@ public sealed partial class SleepingSystem : EntitySystem
/// </summary>
private void OnMobStateChanged(Entity<SleepingComponent> ent, ref MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Dead)
{
RemComp<SpamEmitSoundComponent>(ent);
RemComp<SleepingComponent>(ent);
return;
}
if (TryComp<SpamEmitSoundComponent>(ent, out var spam))
_emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive);
if (args.NewMobState == MobState.Dead)
RemCompDeferred<SleepingComponent>(ent);
}
private void OnStatusMobStateChanged(Entity<ForcedSleepingStatusEffectComponent> ent, ref StatusEffectRelayedEvent<MobStateChangedEvent> args)
{
if (args.Args.NewMobState == MobState.Dead || HasComp<SleepingComponent>(args.Args.Target))
return;
TrySleeping(args.Args.Target);
}
private void OnStatusEffectApplied(Entity<ForcedSleepingStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)

View File

@ -1,4 +1,5 @@
using Content.Shared.Body.Components;
using Content.Shared._DV.Traits.Assorted;
using Content.Shared.Body.Components;
using Content.Shared.Ghost;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
@ -18,7 +19,7 @@ public sealed class BrainSystem : EntitySystem
SubscribeLocalEvent<BrainComponent, OrganGotInsertedEvent>((uid, _, args) => HandleMind(args.Target, uid));
SubscribeLocalEvent<BrainComponent, OrganGotRemovedEvent>((uid, _, args) => HandleMind(uid, args.Target));
SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
SubscribeLocalEvent<BrainComponent, MakeBrainUnborgableEvent>(OnMakeUnborgable);
SubscribeLocalEvent<BrainComponent, BodyRelayedEvent<MakeBrainUnborgableEvent>>(OnMakeUnborgable); // Delta V - Expanded for Delta V MMI Trait
}
private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
@ -44,9 +45,10 @@ public sealed class BrainSystem : EntitySystem
}
// Delta V - Begin Unborgable
private void OnMakeUnborgable(Entity<BrainComponent> ent, ref MakeBrainUnborgableEvent args)
private void OnMakeUnborgable(Entity<BrainComponent> ent, ref BodyRelayedEvent<MakeBrainUnborgableEvent> args)
{
EnsureComp<MindContainerComponent>(ent);
EnsureComp<UnborgableComponent>(ent);
}
// Delta V - End Unborgable
}

View File

@ -420,8 +420,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
component.Critical = true;
component.StaminaDamage = component.CritThreshold;
if (StunSystem.TryUpdateParalyzeDuration(uid, component.StunTime))
StunSystem.TrySeeingStars(uid);
StunSystem.TryUpdateParalyzeDuration(uid, component.StunTime, true);
// Give them buffer before being able to be re-stunned
component.NextUpdate = Timing.CurTime + component.StunTime + StamCritBufferTime;

View File

@ -455,6 +455,8 @@ public abstract partial class SharedDoorSystem : EntitySystem
if (!SetState(uid, DoorState.Closing, door))
return;
SetCollidable(uid, true, door); // DeltaV - doors are solid as soon as we start closing
if (predicted)
Audio.PlayPredicted(door.CloseSound, uid, user, AudioParams.Default.WithVolume(-5));
else if (_net.IsServer)

View File

@ -10,9 +10,11 @@ public abstract class SharedDrunkSystem : EntitySystem
[Dependency] protected readonly StatusEffectsSystem Status = default!;
public override void Initialize()
{
SubscribeLocalEvent<LightweightDrunkComponent, DrunkEvent>(OnLightweightDrinking);
SubscribeLocalEvent<HeavyweightDrunkComponent, DrunkEvent>(OnHeavyweightDrinking); //imp
}
public void TryApplyDrunkenness(EntityUid uid, TimeSpan boozePower)
@ -38,6 +40,12 @@ public abstract class SharedDrunkSystem : EntitySystem
args.Duration *= entity.Comp.BoozeStrengthMultiplier;
}
//imp addition, repeated code bad, i know, but it seems excessive to make a whole other new method that both methods use
private void OnHeavyweightDrinking(Entity<HeavyweightDrunkComponent> entity, ref DrunkEvent args)
{
args.Duration *= entity.Comp.BoozeStrengthMultiplier;
}
[ByRefEvent]
public record struct DrunkEvent(TimeSpan Duration);
}

View File

@ -40,6 +40,7 @@ namespace Content.Shared.Humanoid
yield return HumanoidVisualLayers.FacialHair;
yield return HumanoidVisualLayers.Snout;
yield return HumanoidVisualLayers.SnoutCover;
yield return HumanoidVisualLayers.NeckFluff;
break;
case HumanoidVisualLayers.Snout:
yield return HumanoidVisualLayers.Snout;

View File

@ -42,7 +42,7 @@ public sealed partial class RepairableComponent : Component
/// Tool quality necessary to repair this device.
/// </summary>
[DataField, AutoNetworkedField]
public ProtoId<ToolQualityPrototype> QualityNeeded = "Welding";
public ProtoId<ToolQualityPrototype> QualityNeeded = "Applicating"; // Monolith - Nanite Applicator
/// <summary>
/// The base tool use delay (seconds). This will be modified by the tool's quality

View File

@ -5,6 +5,7 @@ using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Tools.Components; // Monolith - Nanite Applicators
using Content.Shared.Tools.Systems;
using Robust.Shared.Serialization;
@ -115,8 +116,26 @@ public sealed partial class RepairableSystem : EntitySystem
delay *= ent.Comp.SelfRepairPenalty;
}
// BEGIN DeltaV - Scale Repair Time with Damage
// If its a self-repair, ignore it since they've already been penalized.
if (args.User != args.Target && TryComp<DamageableComponent>(args.Target, out var damageComp))
{
// TODO: Scale with the destructible threshold if DestructibleSystem ever gets more prediction added.
// For now, just scale up the delay per 100 damage, or reduce the delay if its less.
var totalDamage = _damageableSystem.GetPositiveDamage((args.Target, damageComp)).GetTotal();
delay *= Math.Clamp((float)totalDamage / 100.0f, 0.5f, 3.0f);
}
// END DeltaV
// BEGIN Monolith - Nanite Applicators
if (!TryComp<ToolComponent>(args.Used, out var tool))
return;
// END Monolith
// Run the repairing doafter
args.Handled = _toolSystem.UseTool(args.Used, args.User, ent.Owner, delay, ent.Comp.QualityNeeded, new RepairDoAfterEvent(), ent.Comp.FuelCost);
}
}

View File

@ -32,7 +32,7 @@ public sealed partial class RevenantComponent : Component
public string SpawnOnDeathPrototype = "EctoplasmRevenant"; // DeltaV
[DataField("stasisTime"), ViewVariables(VVAccess.ReadWrite)] // Begin Imp Changes
public TimeSpan StasisTime = TimeSpan.FromSeconds(60);
public TimeSpan StasisTime = TimeSpan.FromSeconds(100); // DeltaV - change from 60 to 100s
/// <summary>
/// If true, only bible users can exorcise this revenant
@ -50,7 +50,7 @@ public sealed partial class RevenantComponent : Component
/// grinder will explode.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool GrindingRequiresSalt = true; // End Imp Changes
public bool GrindingRequiresSalt = false; // End Imp Changes - DeltaV - true to false
/// <summary>
/// The entity's current max amount of essence. Can be increased

View File

@ -1,3 +1,4 @@
using Content.Shared._CD.Silicons.Borgs; // CosmicDrift - borg subtypes
using Content.Shared.Actions;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
@ -96,6 +97,10 @@ public abstract partial class SharedBorgSwitchableTypeSystem : EntitySystem // D
_userInterface.CloseUi((ent.Owner, null), BorgSwitchableTypeUiKey.SelectBorgType);
UpdateEntityAppearance(ent);
// AL - event for subtype system, always runs at end of borg type code
var ev = new AfterBorgTypeSelectEvent();
RaiseLocalEvent(ent, ref ev);
}
protected void UpdateEntityAppearance(Entity<BorgSwitchableTypeComponent> entity)
@ -120,24 +125,25 @@ public abstract partial class SharedBorgSwitchableTypeSystem : EntitySystem // D
{
footstepModifier.FootstepSoundCollection = prototype.FootstepCollection;
}
if (prototype.SpriteBodyMovementState is { } movementState)
{
var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
spriteMovement.NoMovementLayers.Clear();
spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
{
State = prototype.SpriteBodyState,
};
spriteMovement.MovementLayers.Clear();
spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
{
State = movementState,
};
}
else
{
RemComp<SpriteMovementComponent>(entity);
}
// Start CosmicDrift Changes - Moved to BorgSwitchableTypeSystem.cs
// if (prototype.SpriteBodyMovementState is { } movementState)
// {
// var spriteMovement = EnsureComp<SpriteMovementComponent>(entity);
// spriteMovement.NoMovementLayers.Clear();
// spriteMovement.NoMovementLayers["movement"] = new PrototypeLayerData
// {
// State = prototype.SpriteBodyState,
// };
// spriteMovement.MovementLayers.Clear();
// spriteMovement.MovementLayers["movement"] = new PrototypeLayerData
// {
// State = movementState,
// };
// }
// else
// {
// RemComp<SpriteMovementComponent>(entity);
// }
// End CosmicDrift Changes - Moved to BorgSwitchableTypeSystem.cs
}
}

View File

@ -2,6 +2,7 @@ using Content.Shared._DV.Psionics.Events; // DeltaV - Psionics Refactor
using Content.Shared.Body.Events;
using Content.Shared.Damage.Events;
using Content.Shared.Mobs.Events;
using Content.Shared.Mobs;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Rejuvenate;
@ -23,6 +24,9 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent<StatusEffectContainerComponent, RefreshMovementSpeedModifiersEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, UpdateCanMoveEvent>(RelayStatusEffectEvent);
// By Ref Events
SubscribeLocalEvent<StatusEffectContainerComponent, MobStateChangedEvent>(RefRelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, RefreshFrictionModifiersEvent>(RefRelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, TileFrictionEvent>(RefRelayStatusEffectEvent);

View File

@ -37,7 +37,7 @@ public abstract partial class SharedStunSystem
// Here so server can tell the client to do things
// Don't dirty the component if we don't need to
if (!Appearance.TryGetData<bool>(entity, StunVisuals.SeeingStars, out var stars, entity.Comp) && stars)
if (Appearance.TryGetData<bool>(entity, StunVisuals.SeeingStars, out var stars, entity.Comp) && stars)
return;
if (!Blocker.CanConsciouslyPerformAction(entity))

View File

@ -1,3 +1,4 @@
using System.Globalization;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Alert;
@ -128,25 +129,25 @@ public abstract partial class SharedStunSystem : EntitySystem
}
// TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
public bool TryAddStunDuration(EntityUid uid, TimeSpan duration)
public bool TryAddStunDuration(EntityUid uid, TimeSpan duration, bool visualized = false)
{
if (!_status.TryAddStatusEffectDuration(uid, StunId, duration))
return false;
OnStunnedSuccessfully(uid, duration);
OnStunnedSuccessfully(uid, duration, visualized);
return true;
}
public bool TryUpdateStunDuration(EntityUid uid, TimeSpan? duration)
public bool TryUpdateStunDuration(EntityUid uid, TimeSpan? duration, bool visualized = false)
{
if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration))
return false;
OnStunnedSuccessfully(uid, duration);
OnStunnedSuccessfully(uid, duration, visualized);
return true;
}
private void OnStunnedSuccessfully(EntityUid uid, TimeSpan? duration)
private void OnStunnedSuccessfully(EntityUid uid, TimeSpan? duration, bool visualized)
{
var ev = new StunnedEvent(); // todo: rename event or change how it is raised - this event is raised each time duration of stun was externally changed
RaiseLocalEvent(uid, ref ev);
@ -154,8 +155,11 @@ public abstract partial class SharedStunSystem : EntitySystem
var evDropHands = new DropHandItemsEvent();
RaiseLocalEvent(uid, ref evDropHands);
if (visualized)
TrySeeingStars(uid);
var timeForLogs = duration.HasValue
? duration.Value.Seconds.ToString()
? duration.Value.TotalSeconds.ToString(CultureInfo.CurrentCulture)
: "Infinite";
_adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {timeForLogs} seconds");
}
@ -283,7 +287,7 @@ public abstract partial class SharedStunSystem : EntitySystem
if (time != null)
{
UpdateKnockdownTime((uid, component), time.Value, refresh);
_adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} was knocked down for {time.Value.Seconds} seconds");
_adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} was knocked down for {time.Value.TotalSeconds} seconds");
}
else
{
@ -292,7 +296,7 @@ public abstract partial class SharedStunSystem : EntitySystem
}
}
public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan? duration)
public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan? duration, bool visualized = false)
{
if (duration == null)
return TryUpdateParalyzeDuration(uid, duration);
@ -302,19 +306,19 @@ public abstract partial class SharedStunSystem : EntitySystem
// We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun.
Knockdown(uid, null, false, true, true);
OnStunnedSuccessfully(uid, duration);
OnStunnedSuccessfully(uid, duration, visualized);
return true;
}
public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration)
public bool TryUpdateParalyzeDuration(EntityUid uid, TimeSpan? duration, bool visualized = false)
{
if (!_status.TryUpdateStatusEffectDuration(uid, StunId, duration))
return false;
// We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun.
Knockdown(uid, null, false, true, true);
OnStunnedSuccessfully(uid, duration);
OnStunnedSuccessfully(uid, duration, visualized);
return true;
}

View File

@ -73,4 +73,10 @@ public sealed partial class WelderComponent : Component
/// </summary>
[DataField]
public bool TankSafe;
/// <summary>
/// Monolith - This variable gets rid of the status display.
/// </summary>
[DataField]
public bool OnlyDisplayFuel = false;
}

View File

@ -84,19 +84,33 @@ public abstract partial class SharedToolSystem
{
using (args.PushGroup(nameof(WelderComponent)))
{
if (ItemToggle.IsActivated(entity.Owner))
// BEGIN Monolith - Nanite Applicators
if (!entity.Comp.OnlyDisplayFuel)
{
args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message"));
}
else
{
args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message"));
var lit = Loc.GetString("welder-component-on-examine-welder-not-lit-message");
if (ItemToggle.IsActivated(entity.Owner))
lit = Loc.GetString("welder-component-on-examine-welder-lit-message");
args.PushMarkup(lit);
}
// END Monolith
if (args.IsInDetailsRange)
{
var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp);
// BEGIN Monolith - Nanite Applicator
if (entity.Comp.OnlyDisplayFuel)
{
args.PushMarkup(Loc.GetString("welder-component-on-examine-less-detailed-message",
("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"),
("fuelLeft", fuel),
("fuelCapacity", capacity)));
return;
}
// END Monolith
args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"),
("fuelLeft", fuel),
@ -120,6 +134,19 @@ public abstract partial class SharedToolSystem
&& _whitelist.IsWhitelistPass(tank.FuelWhitelist, entity.Owner) //imp
&& SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solutionComp, out var welderSolution))
{
// BEGIN Monolith - Nanite Applicators
foreach (var reagent in targetSolution.Contents)
{
if (reagent.Reagent.Prototype != entity.Comp.FuelReagent.Id)
{
_popup.PopupClient(
Loc.GetString("welder-component-incompatible-fuel", ("owner", args.Target)), entity, args.User);
return;
}
}
// END Monolith
var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume);
if (trans > 0)
{

View File

@ -0,0 +1,28 @@
using Content.Shared.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CD.Prototypes;
public sealed class ALPrototypeSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
public IEnumerable<(EntityPrototype Prototype, T Component)> EnumerateComponents<T>() where T : IComponent, new()
{
foreach (var entity in _prototype.EnumeratePrototypes<EntityPrototype>())
{
if (entity.TryGetComponent(out T? comp, _compFactory))
yield return (entity, comp);
}
}
public IEnumerable<EntityPrototype> EnumerateEntities<T>() where T : IComponent, new()
{
foreach (var entity in _prototype.EnumeratePrototypes<EntityPrototype>())
{
if (entity.HasComponent<T>(_compFactory))
yield return entity;
}
}
}

View File

@ -0,0 +1,77 @@
using System.Numerics;
using Content.Shared.Inventory;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CD.Silicons.Borgs;
/// <summary>
/// Information relating to a borg's subtype. Should be mostly cosmetic.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[EntityCategory("BorgSubtype")]
public sealed partial class BorgSubtypeDefinitionComponent : Component
{
private static ProtoId<SoundCollectionPrototype> DefaultFootsteps = new("FootstepBorg"); // DeltaV - Removed Readonly
/// <summary>
/// <inheritdoc cref="BorgTypePrototype.InventoryTemplateId"/>
/// </summary>
[DataField, AutoNetworkedField] public ProtoId<InventoryTemplatePrototype> InventoryTemplateId = "borgShort";
/// <summary>
/// The parent borg type of this subtype.
/// </summary>
[DataField(required: true), AutoNetworkedField]
public string ParentType;
/// <summary>
/// <inheritdoc cref="BorgTypePrototype.AddComponents"/>
/// </summary>
[DataField] public ComponentRegistry? AddComponents;
/// <summary>
/// Sprite path that the prototype's layer data will reference.
/// </summary>
[DataField, AutoNetworkedField] public ResPath? SpritePath;
/// <summary>
/// The visual layer data for the subtype.
/// At the minimum should have definitions for each value of <see cref="BorgVisualLayers"/>.
/// </summary>
[DataField(required: true), AutoNetworkedField]
public PrototypeLayerData[] LayerData;
[DataField(required: true), AutoNetworkedField]
public string SpriteHasMindState;
[DataField(required: true), AutoNetworkedField]
public string SpriteNoMindState;
[DataField, AutoNetworkedField] public string? SpriteBodyState;
[DataField, AutoNetworkedField] public Vector2? Offset;
[DataField(required: true), AutoNetworkedField]
public EntProtoId DummyPrototype;
[DataField, AutoNetworkedField] public string PetSuccessString = "petting-success-generic-cyborg";
[DataField, AutoNetworkedField] public string PetFailureString = "petting-failure-generic-cyborg";
/// <summary>
/// Sound specifier for footstep sounds created by this subtype.
/// </summary>
[DataField, AutoNetworkedField]
public SoundSpecifier FootstepCollection { get; set; } = new SoundCollectionSpecifier(DefaultFootsteps);
/// <summary>
/// <inheritdoc cref="BorgTypePrototype.SpriteBodyMovementState"/>
/// </summary>
[DataField, AutoNetworkedField]
public string? SpriteBodyMovementState { get; set; }
}

View File

@ -0,0 +1,18 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CD.Silicons.Borgs;
/// <summary>
/// Component given to borgs that should be able to select subtypes inside of the borg type selection menu.
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState(true)]
public sealed partial class BorgSwitchableSubtypeComponent : Component
{
/// <summary>
/// The <see cref="BorgSubtypeDefinitionComponent"/> of this chassis.
/// </summary>
[DataField, AutoNetworkedField]
public ProtoId<EntityPrototype>? BorgSubtype;
}

View File

@ -0,0 +1,100 @@
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared._CD.Silicons.Borgs;
/// <summary>
/// Shared behaviour for borg switchable subtype logic.
/// Essentially a reimplementation of <see cref="SharedBorgSwitchableTypeSystem"/> specifically for cosmetic functions.
/// </summary>
public abstract class SharedBorgSwitchableSubtypeSystem : EntitySystem
{
[Dependency] private readonly InteractionPopupSystem _interactionPopup = default!;
[Dependency] protected readonly IPrototypeManager Prototypes = default!;
[Dependency] protected readonly IComponentFactory ComponentFactory = default!;
public override void Initialize()
{
SubscribeLocalEvent<BorgSwitchableSubtypeComponent, MapInitEvent>(OnMapInit); // make sure that our subtype is selected first
SubscribeLocalEvent<BorgSwitchableSubtypeComponent, AfterBorgTypeSelectEvent>(OnBorgTypeSelect);
SubscribeLocalEvent<BorgSwitchableSubtypeComponent, TypeTryingToUpdateVisualsEvent>(OnBorgTypeUpdatingVisuals);
Subs.BuiEvents<BorgSwitchableTypeComponent>(BorgSwitchableTypeUiKey.SelectBorgType,
sub =>
{
sub.Event<BorgSelectSubtypeMessage>(SelectSubtypeMessageHandler);
});
base.Initialize();
}
private void OnBorgTypeUpdatingVisuals(Entity<BorgSwitchableSubtypeComponent> ent, ref TypeTryingToUpdateVisualsEvent args)
{
if (!ent.Comp.BorgSubtype.HasValue)
return;
Dirty(ent);
SelectBorgSubtype(ent);
}
private void OnMapInit(Entity<BorgSwitchableSubtypeComponent> ent, ref MapInitEvent args)
{
if (ent.Comp.BorgSubtype != null)
{
SelectBorgSubtype(ent);
}
}
private void OnBorgTypeSelect(Entity<BorgSwitchableSubtypeComponent> ent, ref AfterBorgTypeSelectEvent args)
{
if (!ent.Comp.BorgSubtype.HasValue)
return;
UpdateEntityAppearance(ent);
Dirty(ent);
}
protected virtual void SelectBorgSubtype(Entity<BorgSwitchableSubtypeComponent> ent)
{
UpdateEntityAppearance(ent);
}
private void UpdateEntityAppearance(Entity<BorgSwitchableSubtypeComponent> entity)
{
if (!Prototypes.TryIndex(entity.Comp.BorgSubtype, out var subtypePrototype))
return;
UpdateEntityAppearance(entity, subtypePrototype);
}
protected virtual void UpdateEntityAppearance(Entity<BorgSwitchableSubtypeComponent> entity,
EntityPrototype borgSubtypePrototype)
{
if (!borgSubtypePrototype.TryGetComponent<BorgSubtypeDefinitionComponent>(out var borgSubtype, ComponentFactory))
return;
if (TryComp(entity, out InteractionPopupComponent? popup))
{
_interactionPopup.SetInteractSuccessString((entity.Owner, popup), borgSubtype.PetSuccessString);
_interactionPopup.SetInteractFailureString((entity.Owner, popup), borgSubtype.PetFailureString);
}
if (TryComp(entity, out FootstepModifierComponent? footstepModifier))
{
footstepModifier.FootstepSoundCollection = borgSubtype.FootstepCollection;
}
}
private void SelectSubtypeMessageHandler(EntityUid uid, BorgSwitchableTypeComponent borgSwitchableTypeComponent, BorgSelectSubtypeMessage args)
{
if (!TryComp<BorgSwitchableSubtypeComponent>(uid, out var subtypeComp))
return;
subtypeComp.BorgSubtype = args.Subtype;
Dirty(uid, subtypeComp);
}
}

View File

@ -0,0 +1,16 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CD.Silicons.Borgs;
[Serializable, NetSerializable]
public sealed class BorgSelectSubtypeMessage(ProtoId<EntityPrototype>? subtype) : BoundUserInterfaceMessage
{
public ProtoId<EntityPrototype>? Subtype = subtype;
}
[ByRefEvent]
public record struct AfterBorgTypeSelectEvent;
[ByRefEvent]
public record struct TypeTryingToUpdateVisualsEvent;

View File

@ -222,6 +222,12 @@ public sealed partial class DCCVars
public static readonly CVarDef<bool> EnablePresetCooldowns =
CVarDef.Create("game.enable_preset_cooldowns", false, CVar.SERVERONLY);
/// <summary>
/// The amount of time a player must wait after spawning as a vent critter ghost role before spawning as another one
/// </summary>
public static readonly CVarDef<TimeSpan> VentCritterGhostRoleSpawnCooldown =
CVarDef.Create("ghost.vent_critter_spawn_cooldown", TimeSpan.FromMinutes(3), CVar.SERVERONLY);
/// <summary>
/// A string containing a list of newline-separated strings to be highlighted in the chat. Use this instead of Wizden's CVar.
/// </summary>

View File

@ -0,0 +1,8 @@
namespace Content.Shared._DV.Carrying;
/// <summary>
/// Entities with this component will override the number of free hands required to carry an entity, always requiring one hand instead.
/// Used primarily for entities which only have one hand, but still need to be able to carry.
/// </summary>
[RegisterComponent, Access(typeof(CarryingSystem))]
public sealed partial class CarrierOneHandComponent : Component;

View File

@ -297,8 +297,10 @@ public sealed class CarryingSystem : EntitySystem
if (_net.IsClient) // no spawning prediction
return;
_virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
_virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
for (var x = 0; x < Comp<CarriableComponent>(carried).FreeHandsRequired; x++)
{
_virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
}
}
public bool TryCarry(EntityUid carrier, Entity<CarriableComponent?> toCarry)
@ -358,6 +360,10 @@ public sealed class CarryingSystem : EntitySystem
public bool CanCarry(EntityUid carrier, Entity<CarriableComponent> carried)
{
var handsRequired = carried.Comp.FreeHandsRequired; // imp
if (TryComp<CarrierOneHandComponent>(carrier, out _))// && !carried.Comp.OneHandOverride)
handsRequired = 1;
return
carrier != carried.Owner &&
// can't carry multiple people, even if you have 4 hands it will break invariants when removing carryingcomponent for first carried person
@ -368,7 +374,7 @@ public sealed class CarryingSystem : EntitySystem
!HasComp<BeingCarriedComponent>(carrier) &&
!HasComp<BeingCarriedComponent>(carried) &&
// finally check that there are enough free hands
TryComp<HandsComponent>(carrier, out var hands) && _hands.CountFreeHands((carrier, hands)) >= carried.Comp.FreeHandsRequired;
TryComp<HandsComponent>(carrier, out var hands) && _hands.CountFreeHands((carrier, hands)) >= handsRequired;
}
private float MassContest(EntityUid roller, EntityUid target)

View File

@ -42,6 +42,7 @@ public sealed partial class CosmicCultComponent : Component
[
"ActionCosmicSiphon",
"ActionCosmicBlank",
"ActionCosmicDrawGlyph",
];
[DataField]

View File

@ -18,18 +18,6 @@ public sealed partial class MonumentComponent : Component
[DataField]
public SoundSpecifier InfusionSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/insert_entropy.ogg");
/// <summary>
/// the list of glyphs that this monument is allowed to scribe
/// </summary>
[DataField, AutoNetworkedField]
public HashSet<ProtoId<GlyphPrototype>> UnlockedGlyphs = [];
/// <summary>
/// the glyph that will be scribed when the button is pressed
/// </summary>
[DataField, AutoNetworkedField]
public ProtoId<GlyphPrototype> SelectedGlyph;
/// <summary>
/// the total amount of entropy that has been inserted into the monument
/// </summary>
@ -120,15 +108,6 @@ public sealed class InfluenceSelectedMessage(ProtoId<InfluencePrototype> influen
public ProtoId<InfluencePrototype> InfluenceProtoId = influenceProtoId;
}
[Serializable, NetSerializable]
public sealed class GlyphSelectedMessage(ProtoId<GlyphPrototype> glyphProtoId) : BoundUserInterfaceMessage
{
public ProtoId<GlyphPrototype> GlyphProtoId = glyphProtoId;
}
[Serializable, NetSerializable]
public sealed class GlyphRemovedMessage : BoundUserInterfaceMessage;
[Serializable, NetSerializable]
public enum MonumentVisuals : byte
{

View File

@ -1,10 +1,18 @@
using Content.Shared.Actions;
using Robust.Shared.Map;
using Robust.Shared.GameStates;
namespace Content.Shared._DV.CosmicCult;
[RegisterComponent, NetworkedComponent]
public sealed partial class CosmicCultActionComponent : Component;
[RegisterComponent]
public sealed partial class CosmicGlyphDrawComponent : Component
{
public EntityCoordinates? Target;
}
public sealed partial class EventCosmicSiphon : EntityTargetActionEvent;
public sealed partial class EventCosmicBlank : EntityTargetActionEvent;
public sealed partial class EventCosmicPlaceMonument : InstantActionEvent; //given to the cult leader on roundstart
@ -16,6 +24,7 @@ public sealed partial class EventCosmicIngress : EntityTargetActionEvent;
public sealed partial class EventCosmicImposition : InstantActionEvent;
public sealed partial class EventCosmicNova : WorldTargetActionEvent;
public sealed partial class EventCosmicFragmentation : EntityTargetActionEvent;
public sealed partial class EventCosmicDrawGlyph : WorldTargetActionEvent;
// COLOSSUS ACTIONS
public sealed partial class EventCosmicColossusSunder : WorldTargetActionEvent;

View File

@ -0,0 +1,23 @@
using Content.Shared._DV.CosmicCult.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.CosmicCult;
[Serializable, NetSerializable]
public enum CosmicGlyphDrawUiKey : byte
{
Key,
}
[Serializable, NetSerializable]
public sealed class CosmicGlyphDrawBuiState(List<ProtoId<GlyphPrototype>> glyphs) : BoundUserInterfaceState
{
public List<ProtoId<GlyphPrototype>> Glyphs = glyphs;
}
[Serializable, NetSerializable]
public sealed class CosmicGlyphDrawSelectedMessage(ProtoId<GlyphPrototype> glyphProtoId) : BoundUserInterfaceMessage
{
public ProtoId<GlyphPrototype> GlyphProtoId = glyphProtoId;
}

View File

@ -16,22 +16,16 @@ public sealed class MonumentBuiState : BoundUserInterfaceState
{
public int CurrentProgress;
public int TargetProgress;
public ProtoId<GlyphPrototype> SelectedGlyph;
public HashSet<ProtoId<GlyphPrototype>> UnlockedGlyphs;
public MonumentBuiState(int currentProgress, int targetProgress, int progressOffset, ProtoId<GlyphPrototype> selectedGlyph, HashSet<ProtoId<GlyphPrototype>> unlockedGlyphs)
public MonumentBuiState(int currentProgress, int targetProgress, int progressOffset)
{
CurrentProgress = currentProgress - progressOffset;
TargetProgress = targetProgress - progressOffset;
SelectedGlyph = selectedGlyph;
UnlockedGlyphs = unlockedGlyphs;
}
public MonumentBuiState(MonumentComponent comp)
{
CurrentProgress = comp.CurrentProgress - comp.ProgressOffset;
TargetProgress = comp.TargetProgress - comp.ProgressOffset;
SelectedGlyph = comp.SelectedGlyph;
UnlockedGlyphs = comp.UnlockedGlyphs;
}
}

View File

@ -1,11 +1,7 @@
using Content.Shared._DV.CosmicCult;
using Content.Shared._DV.CosmicCult.Components;
using Content.Shared._DV.CosmicCult.Prototypes;
using Content.Shared.Actions;
using Content.Shared.Movement.Components;
using Content.Shared.Nutrition.Components;
using Content.Shared.UserInterface;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Spawners;
@ -21,16 +17,12 @@ public abstract class SharedMonumentSystem : EntitySystem
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
[Dependency] private readonly SharedCosmicGlyphSystem _glyph = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MonumentComponent, BoundUIOpenedEvent>(OnUIOpened);
SubscribeLocalEvent<MonumentComponent, GlyphSelectedMessage>(OnGlyphSelected);
SubscribeLocalEvent<MonumentComponent, GlyphRemovedMessage>(OnGlyphRemove);
SubscribeLocalEvent<MonumentComponent, InfluenceSelectedMessage>(OnInfluenceSelected);
SubscribeLocalEvent<MonumentOnDespawnComponent, TimedDespawnEvent>(OnTimedDespawn);
SubscribeLocalEvent<MonumentCollisionComponent, PreventCollideEvent>(OnPreventCollide);
@ -80,38 +72,6 @@ public abstract class SharedMonumentSystem : EntitySystem
}
#region UI listeners
private void OnGlyphSelected(Entity<MonumentComponent> ent, ref GlyphSelectedMessage args)
{
ent.Comp.SelectedGlyph = args.GlyphProtoId;
if (!_prototype.TryIndex(args.GlyphProtoId, out var proto))
return;
var xform = Transform(ent);
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return;
var localTile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
var targetIndices = localTile.GridIndices + new Vector2i(0, -1);
if (ent.Comp.CurrentGlyph is { } curGlyph) _glyph.EraseGlyph(curGlyph);
var glyphEnt = Spawn(proto.Entity, _map.ToCenterCoordinates(xform.GridUid.Value, targetIndices, grid));
ent.Comp.CurrentGlyph = glyphEnt;
var evt = new CosmicCultAssociateRuleEvent(ent, glyphEnt);
RaiseLocalEvent(ref evt);
_ui.SetUiState(ent.Owner, MonumentKey.Key, new MonumentBuiState(ent.Comp));
}
private void OnGlyphRemove(Entity<MonumentComponent> ent, ref GlyphRemovedMessage args)
{
if (ent.Comp.CurrentGlyph is { } curGlyph) _glyph.EraseGlyph(curGlyph);
_ui.SetUiState(ent.Owner, MonumentKey.Key, new MonumentBuiState(ent.Comp));
}
private void OnInfluenceSelected(Entity<MonumentComponent> ent, ref InfluenceSelectedMessage args)
{
if (!_prototype.TryIndex(args.InfluenceProtoId, out var proto) || !TryComp<ActivatableUIComponent>(ent, out var uiComp) || !TryComp<CosmicCultComponent>(args.Actor, out var cultComp))

View File

@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Ghost.Roles;
[Serializable, NetSerializable]
public sealed class DVSpawnableGhostRoleRequestEvent(ProtoId<DVSpawnableGhostRolePrototype> prototype) : EntityEventArgs
{
public ProtoId<DVSpawnableGhostRolePrototype> Prototype = prototype;
}
[Serializable, NetSerializable]
public sealed class DVSpawnableGhostRoleCooldownRequestEvent : EntityEventArgs;
[Serializable, NetSerializable]
public sealed class DVSpawnableGhostRoleCooldownUpdateEvent(TimeSpan? cooldownEnd) : EntityEventArgs
{
public TimeSpan? CooldownEnd = cooldownEnd;
}

View File

@ -0,0 +1,23 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Ghost.Roles;
[Prototype("dvSpawnableGhostRole")]
public sealed partial class DVSpawnableGhostRolePrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
public LocId Name => $"dv-spawnable-{ID}.name";
public LocId Description => $"dv-spawnable-{ID}.description";
[DataField(required: true)]
public LocId Rules;
[DataField]
public List<EntProtoId> MindRoles = new() { "MindRoleGhostRoleNeutral" };
[DataField(required: true)]
public EntProtoId Entity;
}

View File

@ -68,6 +68,7 @@ public sealed class ChargeHolosignSystem : EntitySystem
var holoUid = PredictedSpawnAtPosition(ent.Comp1.SignProto, args.ClickLocation.SnapToGrid(EntityManager));
var xform = Transform(holoUid);
xform.LocalRotation = Angle.Zero;
if (!xform.Anchored)
_transform.AnchorEntity(holoUid, xform); // anchor to prevent any tempering with (don't know what could even interact with it)

View File

@ -0,0 +1,15 @@
using Content.Shared.EntityTable.EntitySelectors;
using Robust.Shared.GameStates;
namespace Content.Shared._DV.InnateTools;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(DVInnateToolsSystem))]
public sealed partial class DVInnateToolsComponent : Component
{
[DataField(required: true)]
public EntityTableSelector Tools;
[DataField, AutoNetworkedField]
public int Provided;
}

View File

@ -0,0 +1,58 @@
using System.Linq;
using Content.Shared.EntityTable;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Components;
namespace Content.Shared._DV.InnateTools;
public sealed class DVInnateToolsSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly EntityTableSystem _entityTable = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVInnateToolsComponent, MapInitEvent>(OnInnateToolsInit);
SubscribeLocalEvent<DVInnateToolsComponent, ComponentShutdown>(OnInnateToolsShutdown);
}
private void OnInnateToolsInit(Entity<DVInnateToolsComponent> ent, ref MapInitEvent args)
{
var items = _entityTable.GetSpawns(ent.Comp.Tools).ToList();
var xform = Transform(ent);
for (var i = 0; i < items.Count; i++)
{
var name = $"{GetNetEntity(ent)}-innate-{i}";
_hands.AddHand(ent.Owner,
name,
i switch
{
0 => HandLocation.Right,
_ when i < items.Count-1 => HandLocation.Middle,
_ => HandLocation.Left,
});
var spawned = PredictedSpawnAtPosition(items[i], xform.Coordinates);
_hands.DoPickup(ent.Owner, name, spawned);
EnsureComp<UnremoveableComponent>(spawned);
}
ent.Comp.Provided = items.Count;
Dirty(ent);
}
private void OnInnateToolsShutdown(Entity<DVInnateToolsComponent> ent, ref ComponentShutdown args)
{
for (var i = 0; i < ent.Comp.Provided; i++)
{
var name = $"{GetNetEntity(ent)}-innate-{i}";
if (_hands.TryGetHeldItem(ent.Owner, name, out var held))
QueueDel(held);
}
}
}

View File

@ -9,7 +9,7 @@ using Robust.Shared.Random;
namespace Content.Shared._DV.IonLaws;
public sealed class IonLawSystem : EntitySystem
public sealed class DVIonLawSystem : EntitySystem
{
[Dependency] private readonly ILocalizationManager _localization = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;

Some files were not shown because too many files have changed in this diff Show More