Merge remote-tracking branch 'upstream/master' into Add-Hysterical-Strength

This commit is contained in:
Sir Warock 2026-08-11 01:04:09 +02:00
commit 2d96945b9f
782 changed files with 11144 additions and 3716 deletions

View File

@ -2,6 +2,7 @@ using Content.Client.Cargo.UI;
using Content.Shared.Cargo.Components;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Content.Shared._DV.Cargo.Components; // DeltaV: Bounty claim messages
namespace Content.Client.Cargo.BUI;
@ -30,6 +31,18 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
{
SendMessage(new BountySkipMessage(id));
};
// DeltaV: bounty claim stuff begins
_menu.OnClaimButtonPressed += id =>
{
SendMessage(new BountyClaimedMessage(id));
};
_menu.OnStatusOptionSelected += (id, status) =>
{
SendMessage(new BountySetStatusMessage(id, status));
};
// DeltaV: bounty claim stuff ends
}
protected override void UpdateState(BoundUserInterfaceState message)

View File

@ -10,6 +10,8 @@
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<RichTextLabel Name="RewardLabel"/>
<RichTextLabel Name="ManifestLabel"/>
<RichTextLabel Name="ClaimedBylabel"/> <!-- DeltaV bounty claim -->
<RichTextLabel Name="StatusLabel"/> <!-- DeltaV bounty claim -->
</BoxContainer>
<Control MinWidth="10"/>
<BoxContainer Orientation="Vertical" MinWidth="120">
@ -17,14 +19,22 @@
<Button Name="PrintButton"
Text="{Loc 'bounty-console-label-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Right"
StyleClasses="OpenRight"/>
StyleClasses="OpenBoth"/>
<Button Name="SkipButton"
Text="{Loc 'bounty-console-skip-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Right"
HorizontalExpand="True"
StyleClasses="OpenLeft"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" MinWidth="120"> <!-- Begin DeltaV bounty claim content -->
<Button Name="ClaimButton"
Text= "{Loc 'bounty-console-claim-button-text'}"
HorizontalExpand="False"
StyleClasses="OpenRight"/>
<OptionButton Name="BountyStatusSelector"
Access="Public"
StyleClasses="OpenBoth"
HorizontalExpand="True"/>
</BoxContainer> <!-- End DeltaV content-->
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
</BoxContainer>
</BoxContainer>

View File

@ -17,6 +17,8 @@ public sealed partial class BountyEntry : BoxContainer
public Action? OnLabelButtonPressed;
public Action? OnSkipButtonPressed;
public Action? OnClaimButtonPressed; // DeltaV
public Action? OnStatusOptionSelected; // DeltaV
public TimeSpan EndTime;
public TimeSpan UntilNextSkip;
@ -45,6 +47,20 @@ public sealed partial class BountyEntry : BoxContainer
PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
// Begin DeltaV bounty claiming
ClaimButton.OnPressed += _ => OnClaimButtonPressed?.Invoke();
BountyStatusSelector.AddItem(Loc.GetString($"bounty-console-status-{nameof(CargoBountyStatus.Undelivered)}"), 0);
BountyStatusSelector.AddItem(Loc.GetString($"bounty-console-status-{nameof(CargoBountyStatus.Waiting)}"), 1);
BountyStatusSelector.AddItem(Loc.GetString($"bounty-console-status-{nameof(CargoBountyStatus.OnShuttle)}"), 2);
BountyStatusSelector.Select((int) bounty.Status);
BountyStatusSelector.ToolTip = Loc.GetString($"bounty-console-status-tooltip-{bounty.Status.ToString()}");
var claimedByText = string.IsNullOrEmpty(bounty.ClaimedBy) ? Loc.GetString("bounty-console-claimed-by-none") : bounty.ClaimedBy;
ClaimedBylabel.SetMarkup(Loc.GetString("bounty-console-claimed-by", ("claimant", claimedByText)));
StatusLabel.SetMarkup(Loc.GetString($"bounty-console-formatted-status-{bounty.Status.ToString()}"));
// End DeltaV bounty claiming
}
private void UpdateSkipButton(float deltaSeconds)

View File

@ -1,8 +1,9 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
<!--DeltaV - SetSize was 550, now 750 to accomodate bounty claim UI -->
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'bounty-console-menu-title'}"
SetSize="550 420"
SetSize="750 420"
MinSize="400 350">
<BoxContainer Orientation="Vertical"
VerticalExpand="True"

View File

@ -11,6 +11,8 @@ public sealed partial class CargoBountyMenu : FancyWindow
{
public Action<string>? OnLabelButtonPressed;
public Action<string>? OnSkipButtonPressed;
public Action<string>? OnClaimButtonPressed; // DeltaV
public Action<string, int>? OnStatusOptionSelected; // DeltaV
public CargoBountyMenu()
{
@ -31,6 +33,8 @@ public sealed partial class CargoBountyMenu : FancyWindow
var entry = new BountyEntry(b, untilNextSkip);
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
entry.OnClaimButtonPressed += () => OnClaimButtonPressed?.Invoke(b.Id); // DeltaV
entry.BountyStatusSelector.OnItemSelected += args => OnStatusOptionSelected?.Invoke(b.Id, args.Id); // DeltaV
BountyEntriesContainer.AddChild(entry);
}

View File

@ -59,9 +59,12 @@
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
<Label Name="TemperatureLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
<Label Name="BloodLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<Label Name="DamageLabel" />
<!-- Begin DeltaV - Health Analyzer Plus -->
<Label Name="BloodLevelLabel" />
<!-- Label Name="BloodLabel" / -->
<!-- Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" / -->
<!-- Label Name="DamageLabel" / -->
<!-- End DeltaV - Health Analyzer Plus -->
</GridContainer>
</BoxContainer>
@ -73,10 +76,41 @@
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
<!-- Begin DeltaV - Health Analyzer Plus -->
<!-- BoxContainer Name="GroupsContainer" Margin="0 5 0 5" Orientation="Vertical" -->
<!-- /BoxContainer -->
<GridContainer Margin="0 5 0 5" Columns="3" HorizontalAlignment="Center">
<BoxContainer Name="DamageBox" Margin="5 5 5 5" Orientation="Vertical">
<Label Name="TotalDamageLabel" HorizontalAlignment="Center" />
<PanelContainer Name="DamageGroupsDivider" StyleClasses="LowDivider" />
<Label
Name="NoDamageLabel"
HorizontalAlignment="Center"
Text="{Loc 'health-analyzer-plus-window-no-damage-text'}"
FontColorOverride="DeepSkyBlue"
Margin="0 5 0 5" />
<BoxContainer HorizontalAlignment="Center">
<BoxContainer Name="DamageGroupsContainer" Margin="0 5 0 5" Orientation="Vertical" />
</BoxContainer>
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Name="BloodstreamBox" Margin="5 5 5 5" Orientation="Vertical">
<Label Name="TotalReagentQuantityLabel" HorizontalAlignment="Center" />
<PanelContainer Name="BloodstreamReagentsDivider" StyleClasses="LowDivider" />
<Label
Name="NoReagentsLabel"
HorizontalAlignment="Center"
Text="{Loc 'health-analyzer-plus-window-no-reagents-text'}"
FontColorOverride="DeepSkyBlue"
Margin="0 5 0 5" />
<BoxContainer HorizontalAlignment="Center">
<BoxContainer Name="BloodstreamReagentListContainer" Orientation="Vertical" Margin="0 5 0 5" />
</BoxContainer>
</BoxContainer>
</GridContainer>
<!-- End DeltaV - Health Analyzer Plus -->
</BoxContainer>

View File

@ -19,12 +19,25 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
// BEGIN DeltaV
using Content.Client._DV.Traits.Assorted;
using Content.Shared._DV.Traits.Assorted;
using Content.Shared._DV.Medical; // Uncloneable
using Content.Shared._DV.MedicalRecords; // Medical Records
// Health Analyzer Plus
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Body.Systems;
using Content.Client.Body.Systems;
using Content.Shared.Chemistry.EntitySystems;
using Content.Client.Chemistry.Containers.EntitySystems;
using Content.Shared.Body.Components;
// END DeltaV
namespace Content.Client.HealthAnalyzer.UI;
using Content.Client._DV.Traits.Assorted; // DeltaV
using Content.Shared._DV.Traits.Assorted; // DeltaV
using Content.Shared._DV.Medical; // DeltaV - Uncloneable
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
// Health analyzer UI is split from its window because it's used by both the
// health analyzer item and the cryo pod UI.
@ -32,6 +45,8 @@ using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerControl : BoxContainer
{
private const string REAGENT_GROUP_MEDICINE = "Medicine"; // DeltaV - Health Analyzer
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
private readonly IPrototypeManager _prototypes;
@ -41,6 +56,8 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
private readonly UnborgableSystem _unborgable; // DeltaV
private readonly RedshirtSystem _redshirt; // DeltaV
private readonly UncloneableSystem _uncloneable; // DeltaV
private readonly SharedBloodstreamSystem _bloodstream; // DeltaV
private readonly SharedSolutionContainerSystem _solutionContainer; // DeltaV
// Begin DeltaV - Medical Records
private readonly ButtonGroup _triageStatusGroup = new();
@ -66,6 +83,8 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
_uncloneable = _entityManager.System<UncloneableSystem>(); // DeltaV
_bloodstream = _entityManager.System<BloodstreamSystem>(); // DeltaV
_solutionContainer = _entityManager.System<SolutionContainerSystem>(); // DeltaV
// Begin DeltaV - Medical Records
foreach (var item in Enum.GetValues<TriageStatus>())
@ -138,9 +157,13 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
? $"{state.Temperature - Atmospherics.T0C:F1} °C ({state.Temperature:F1} K)"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
// Begin DeltaV - Health Analyzer Plus
/*
BloodLabel.Text = !float.IsNaN(state.BloodLevel)
? $"{state.BloodLevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
*/
// End DeltaV - Health Analyzer Plus
StatusLabel.Text =
_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
@ -149,7 +172,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
// Total Damage
DamageLabel.Text = _damageable.GetTotalDamage(target.Value).ToString();
// DamageLabel.Text = _damageable.GetTotalDamage(target.Value).ToString(); // DeltaV - Health Analyzer Plus
// Alerts
// DeltaV traits - This is going to be horrid if we just keep adding things like this.
@ -216,6 +239,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
var damagePerType = _damageable.GetAllDamage(target.Value).DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
DrawBloodstreamInfo(target.Value, state.BloodSolution, state.BloodLevel); // End DeltaV - Health Analyzer Plus
// Begin DeltaV - Medical Records
if (state.MedicalRecord is not { } records)
@ -254,7 +278,15 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> groups,
IReadOnlyDictionary<ProtoId<DamageTypePrototype>, FixedPoint2> damageDict)
{
GroupsContainer.RemoveAllChildren();
// Begin DeltaV - Health Analyzer Plus
// GroupsContainer.RemoveAllChildren();
DamageGroupsContainer.RemoveAllChildren();
// Begin DeltaV - Health Analyzer Plus
var totalDamage = damageDict.Sum(x => x.Value.Double());
TotalDamageLabel.Text = Loc.GetString("health-analyzer-plus-window-entity-total-damage-text", ("amount", Math.Round(totalDamage, 2)));
NoDamageLabel.Visible = totalDamage == 0;
// End DeltaV - Health Analyzer Plus
foreach (var (damageGroupId, damageAmount) in groups)
{
@ -275,7 +307,8 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
// GroupsContainer.AddChild(groupContainer); // DeltaV - Health Analyzer Plus
DamageGroupsContainer.AddChild(groupContainer); // DeltaV - Health Analyzer Plus
// Show the damage for each type in that group.
var group = _prototypes.Index(damageGroupId);
@ -296,6 +329,116 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
}
}
// Begin DeltaV - Health Analyzer Plus
private void DrawBloodstreamInfo(EntityUid target, Solution? bloodSolution, float bloodlevel)
{
BloodstreamReagentListContainer.RemoveAllChildren();
List<ReagentQuantity> bloodstream = new();
double totalReagentQuantity = 0;
double unknownReagents = 0;
// Make sure we can access the bloodtype and reagents before trying to use them
if (bloodSolution is not null)
{
if (!_entityManager.TryGetComponent<BloodstreamComponent>(target, out var bloodstreamComp) ||
!_solutionContainer.ResolveSolution(target, bloodstreamComp.BloodSolutionName, ref bloodstreamComp.BloodSolution, out var bloodReagentSolution))
return;
// Get the blood reagent IDs to ignore in the scan
var bloodReagentIds = bloodstreamComp.BloodReferenceSolution.Select(b => b.Reagent);
// Get all of the medicines in the bloodstream
foreach (var reagent in bloodSolution!.Contents)
{
if (bloodReagentIds.Contains(reagent.Reagent))
continue;
if (_prototypes.Index<ReagentPrototype>(reagent.Reagent.Prototype).Group == REAGENT_GROUP_MEDICINE)
{
bloodstream.Add(reagent);
totalReagentQuantity += reagent.Quantity.Double();
}
else
{
unknownReagents += reagent.Quantity.Double();
}
}
}
var bloodQuantityPercent = !float.IsNaN(bloodlevel)
? $"{bloodlevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
// Display the percent and amount of blood in the system
// Normal health scanner is percent only, this one adds the actual units too
BloodLevelLabel.Text = Loc.GetString(
"health-analyzer-plus-window-entity-blood-level-quantity-text",
("percent", bloodQuantityPercent)
);
// Display the total amount of reagents in the bloodstream, 0 is valid
TotalReagentQuantityLabel.Text = Loc.GetString(
"health-analyzer-plus-window-entity-total-reagents-text",
("amount", Math.Round(totalReagentQuantity, 1).ToString("0.0"))
);
// If the bloodstream was empty, display a message instead to fill the space with something
NoReagentsLabel.Visible = bloodstream.Count == 0 && unknownReagents == 0;
// For each of the reagents in the bloodstream, add them to the list of reagents
// If there are none, this loop will simply do nothing
foreach (var (reagentID, reagentQuantity) in bloodstream)
{
var reagentPrototype = _prototypes.Index<ReagentPrototype>(reagentID.Prototype);
// Make a new container for each reagent we add to the list
var reagentContainer = new BoxContainer
{
Align = AlignMode.Begin,
Orientation = LayoutOrientation.Vertical
};
// Add the string "[] <reagent_name>: <reagent_amount>u" for the current reagent
// The [] is a unicode block character, and is colored with the reagent color
// to make it easier to identify
reagentContainer.AddChild(
new RichTextLabel
{
Text = Loc.GetString("health-analyzer-plus-window-reagent-text",
("reagentColor", reagentPrototype.SubstanceColor),
("reagentName", reagentPrototype.LocalizedName),
("amount", Math.Round(reagentQuantity.Double(), 1).ToString("0.0"))
)
}
);
BloodstreamReagentListContainer.AddChild(reagentContainer);
}
// Add unknown reagents (if any)
if (unknownReagents > 0)
{
var unknownReagentContainer = new BoxContainer
{
Align = AlignMode.Begin,
Orientation = LayoutOrientation.Vertical
};
unknownReagentContainer.AddChild(
new RichTextLabel
{
Text = Loc.GetString("health-analyzer-plus-window-unknown-reagents-text",
("amount", Math.Round(unknownReagents, 1).ToString("0.0"))
)
}
);
BloodstreamReagentListContainer.AddChild(unknownReagentContainer);
}
return;
}
// End DeltaV - Health Analyzer Plus
private Texture GetTexture(string texture)
{
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");

View File

@ -7,7 +7,9 @@
<ScrollContainer
Margin="5 5 5 5"
ReturnMeasure="True"
VerticalExpand="True">
VerticalExpand="True"
VScrollBarHidden="True"
HScrollBarHidden="True"> <!-- DeltaV - Hide the scroll bars -->
<ui:HealthAnalyzerControl
Name="HealthAnalyzer"

View File

@ -34,6 +34,9 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
common.AddFunction(ContentKeyFunctions.SaveItemLocation);
common.AddFunction(ContentKeyFunctions.Point);
// Floofstation section
common.AddFunction(ContentKeyFunctions.OfferItem);
// Floofstation section end
common.AddFunction(ContentKeyFunctions.ZoomOut);
common.AddFunction(ContentKeyFunctions.ZoomIn);
common.AddFunction(ContentKeyFunctions.ResetZoom);

View File

@ -9,6 +9,8 @@ using Content.Shared.Preferences;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Content.Client._CD.Records.UI; // DeltaV
using Content.Shared._DV.Body.Systems; // DeltaV
namespace Content.Client.Lobby.UI;
@ -193,6 +195,7 @@ public sealed partial class HumanoidProfileEditor
RefreshLoadouts();
UpdateSexControls(); // update sex for new species
UpdateSpeciesGuidebookIcon();
UpdateHeightControls(); // DeltaV
ReloadPreview();
}
@ -204,23 +207,22 @@ public sealed partial class HumanoidProfileEditor
ReloadProfilePreview();
}
private void UpdateHeightControls()
{
if (Profile == null)
{
return;
}
var species = _species.Find(x => x.ID == Profile.Species);
if (species != null)
_defaultHeight = species.DefaultHeight;
var prototype = _prototypeManager.Index(Profile.Species);
_defaultHeight = prototype.DefaultHeight;
var prototype = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
var sliderPercent = (Profile.Height - prototype.MinHeight) /
(prototype.MaxHeight - prototype.MinHeight);
CDHeightSlider.Value = sliderPercent;
CDHeight.Text = Profile.Height.ToString(CultureInfo.InvariantCulture);
var scaleReference = _defaultHeight * prototype.BaseScale.Y;
var newHeight = MathF.Round(MathHelper.Lerp(prototype.MinHeight, prototype.MaxHeight, sliderPercent), 2);
CDHeightLabel.Text = UnitConversion.GetMetricAndImperialDisplayFromScale(scaleReference * newHeight);
CDPullSpeedReductionLabel.Text = SmallCharacterSystem.GetPullSpeedPenaltyDisplayFromScale(newHeight);
}
// End CD - Character Records

View File

@ -73,9 +73,13 @@
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-height-label'}" />
<Control HorizontalExpand="True" />
<Label Name="CDHeightLabel" />
<Slider Name="CDHeightSlider" HorizontalAlignment="Right" SetWidth="300" MinValue="0.0" MaxValue="1.0"/>
<LineEdit HorizontalAlignment="Right" Name="CDHeight" MinSize="60 0" Text="1.0" />
<Button Name="CDHeightReset" Text="{Loc 'humanoid-profile-editor-reset-height-button'}" HorizontalAlignment="Right"/>
</BoxContainer>
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-height-pull-speed-penalty-label'}" />
<Control HorizontalExpand="True" />
<Label Name="CDPullSpeedReductionLabel" />
</BoxContainer>
<!-- End CD - Character Records -->
<!-- Sex -->

View File

@ -2,9 +2,6 @@ using Content.Client.Humanoid;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Sprite;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._DV.Species; // DeltaV - Species hider
using Content.Shared.Body;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
@ -22,13 +19,8 @@ using Robust.Shared.ContentPack;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Direction = Robust.Shared.Maths.Direction;
// Begin CD - Character Records
using System.Globalization;
using Content.Client._CD.Records.UI;
using Content.Shared._CD.Records;
// End CD - Character Records
using Content.Shared._DV.Traits;
using Content.Shared.Humanoid.Prototypes; // DV - Traits
using Content.Client._CD.Records.UI; // CD - Character Records
using Content.Shared._DV.Body.Systems; // DV - Traits
namespace Content.Client.Lobby.UI
{
@ -221,34 +213,18 @@ namespace Content.Client.Lobby.UI
// Begin CD - Character Records
#region CDHeight
CDHeight.OnTextChanged += args =>
{
if (Profile is null || !float.TryParse(args.Text, out var newHeight))
return;
var prototype = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
newHeight = MathF.Round(Math.Clamp(newHeight, prototype.MinHeight, prototype.MaxHeight), 2);
// The percentage between the start and end numbers, aka "inverse lerp"
var sliderPercent = (newHeight - prototype.MinHeight) /
(prototype.MaxHeight - prototype.MinHeight);
CDHeightSlider.Value = sliderPercent;
SetProfileHeight(newHeight);
};
CDHeightReset.OnPressed += _ =>
{
CDHeight.SetText(_defaultHeight.ToString(CultureInfo.InvariantCulture), true);
};
CDHeightSlider.OnValueChanged += _ =>
{
if (Profile is null)
return;
var prototype = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
var prototype = _prototypeManager.Index(Profile.Species);
var newHeight = MathF.Round(MathHelper.Lerp(prototype.MinHeight, prototype.MaxHeight, CDHeightSlider.Value), 2);
CDHeight.Text = newHeight.ToString(CultureInfo.InvariantCulture);
var speciesScale = prototype.BaseScale.Y;
CDHeightLabel.Text = UnitConversion.GetMetricAndImperialDisplayFromScale(newHeight * speciesScale);
CDPullSpeedReductionLabel.Text = SmallCharacterSystem.GetPullSpeedPenaltyDisplayFromScale(newHeight);
SetProfileHeight(newHeight);
};

View File

@ -75,9 +75,11 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl
if (!LocalizedNames.TryGetValue(netEntity, out var name))
name = Loc.GetString("navmap-unknown-entity");
var pos = _xform.ToMapCoordinates(blip.Coordinates); // DeltaV - map-coordinates
var message = name + "\n" + Loc.GetString("navmap-location",
("x", MathF.Round(blip.Coordinates.X)),
("y", MathF.Round(blip.Coordinates.Y)));
("x", MathF.Round(pos.X)), // DeltaV - map-coordinates
("y", MathF.Round(pos.Y))); // DeltaV - map-coordinates
_trackedEntityLabel.Text = message;
_trackedEntityPanel.Visible = true;

View File

@ -88,9 +88,9 @@ public sealed partial class CryoPodWindow : FancyWindow
// Health analyzer
var maybePatient = _entityManager.GetEntity(msg.Health.TargetEntity);
var hasPatient = msg.Health.TargetEntity.HasValue;
var hasDamage = hasPatient && msg.HasDamage;
// var hasDamage = hasPatient && msg.HasDamage; // DeltaV - Health Analyzer Plus
NoDamageText.Visible = (hasPatient && !hasDamage);
NoDamageText.Visible = false; //(hasPatient && !hasDamage); // DeltaV - Health Analyzer Plus
HealthSection.Visible = hasPatient;
EjectPatientButton.Disabled = !hasPatient;

View File

@ -208,6 +208,7 @@ namespace Content.Client.Options.UI.Tabs
AddButton(ContentKeyFunctions.MovePulledObject);
AddButton(ContentKeyFunctions.ReleasePulledObject);
AddButton(ContentKeyFunctions.Point);
AddButton(ContentKeyFunctions.OfferItem); // Floofstation
AddButton(ContentKeyFunctions.RotateObjectClockwise);
AddButton(ContentKeyFunctions.RotateObjectCounterclockwise);
AddButton(ContentKeyFunctions.FlipObject);

View File

@ -1,9 +1,8 @@
<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="600 480">
SetSize="550 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
<!-- Left pane: selection of borg type -->
@ -30,9 +29,6 @@
<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,7 +1,6 @@
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;
@ -30,7 +29,6 @@ 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" };
@ -58,13 +56,6 @@ 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)
@ -96,14 +87,6 @@ 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)
@ -111,7 +94,6 @@ public sealed partial class BorgSelectTypeMenu : FancyWindow
if (_selectedBorgType == null)
return;
ConfirmedBorgSubtype?.Invoke(ChassisSpriteSelection.SubtypePrototype); // CosmicDrift
ConfirmedBorgType?.Invoke(_selectedBorgType);
}

View File

@ -1,6 +1,4 @@
using Content.Shared._CD.Silicons; // CosmicDrift
using Content.Shared._CD.Silicons.Borgs; // CosmicDrift
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Silicons.Borgs.Components;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
@ -28,6 +26,5 @@ 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,12 +1,9 @@
using Content.Client.PDA; // DeltaV
using Content.Shared._CD.Silicons.Borgs; // CosmicDrift
using Content.Client.PDA; // DeltaV
using Content.Shared.Movement.Components;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement; // CosmicDrift
using Robust.Shared.Serialization.TypeSerializers.Implementations; // CosmicDrift
using Robust.Shared.Timing; // CosmicDrift
using Robust.Shared.Serialization; // DeltaV
namespace Content.Client.Silicons.Borgs;
@ -20,7 +17,6 @@ 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()
{
@ -44,20 +40,15 @@ public sealed partial class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeS
Entity<BorgSwitchableTypeComponent> entity,
BorgTypePrototype prototype)
{
// 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
// Begin DeltaV Additions
if (prototype.ClientComponents is {} add)
EntityManager.AddComponents(entity, add);
// End DeltaV Additions
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);
}
@ -85,26 +76,6 @@ 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

@ -49,11 +49,6 @@
<Control HorizontalExpand="True" />
<Label Name="RecordContainerSpecies" Align="Right" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-height'}"/>
<Control HorizontalExpand="True" />
<Label Name="RecordContainerHeight" Align="Right" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-weight'}"/>
<Control HorizontalExpand="True" />

View File

@ -325,8 +325,7 @@ public sealed partial class CharacterRecordViewer : FancyWindow
RecordContainerJob.Text = record.JobTitle; /* At some point in the future we might want to display the icon */
RecordContainerGender.Text = record.Gender.ToString();
RecordContainerSpecies.Text = record.Species;
RecordContainerHeight.Text = cr.Height + " " + UnitConversion.GetImperialDisplayLength(cr.Height);
RecordContainerWeight.Text = cr.Weight + " " + UnitConversion.GetImperialDisplayMass(cr.Weight);
RecordContainerWeight.Text = $"{cr.Weight} ({UnitConversion.GetImperialDisplayMass(cr.Weight)})";
RecordContainerContactName.SetValue(cr.EmergencyContactName);
RecordContainerEmployment.Visible = false;

View File

@ -5,12 +5,6 @@
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="10">
<!-- Height, Weight -->
<GridContainer Columns="2">
<BoxContainer HorizontalExpand="True" SeparationOverride="2">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-height'}" />
<Control HorizontalExpand="True" MinSize="5 0" />
<LineEdit Name="HeightEdit" HorizontalAlignment="Right" MinSize="60 0" />
<Label Name="HeightImperialLabel" MinWidth="60" />
</BoxContainer>
<BoxContainer HorizontalExpand="True" SeparationOverride="2">
<Label Text="{Loc 'humanoid-profile-editor-cd-records-weight'}" />
<Control HorizontalExpand="True" MinSize="5 0" />

View File

@ -25,14 +25,6 @@ public sealed partial class RecordEditorGui : Control
#region General
HeightEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var newHeight))
return;
UpdateImperialHeight(newHeight);
UpdateRecords(_records.WithHeight(newHeight));
};
WeightEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var newWeight))
@ -128,8 +120,6 @@ public sealed partial class RecordEditorGui : Control
private void UpdateWidgets()
{
HeightEdit.SetText(_records.Height.ToString());
UpdateImperialHeight(_records.Height);
WeightEdit.SetText(_records.Weight.ToString());
UpdateImperialWeight(_records.Weight);
ContactNameEdit.SetText(_records.EmergencyContactName);
@ -143,13 +133,8 @@ public sealed partial class RecordEditorGui : Control
PostmortemEdit.SetText(_records.PostmortemInstructions);
}
private void UpdateImperialHeight(int newHeight)
{
HeightImperialLabel.Text = UnitConversion.GetImperialDisplayLength(newHeight);
}
private void UpdateImperialWeight(int newWeight)
{
WeightImperialLabel.Text = UnitConversion.GetImperialDisplayMass(newWeight);
WeightImperialLabel.Text = $"({UnitConversion.GetImperialDisplayMass(newWeight)})";
}
}

View File

@ -2,15 +2,47 @@ namespace Content.Client._CD.Records.UI;
public static class UnitConversion
{
/// <summary>
/// DeltaV - The average height of a human in centimeters. According to the US CDC, its
/// 171 for men and 160 for women. So average of that is ~165cm.
///
/// Just kidding, we're going with EE's arbitrary standard of 175cm.
/// </summary>
private const int AVERAGE_HEIGHT_CM = 175;
/// <summary>
/// DeltaV - 1.0 scale is considered average for humans, so a scale of 1 will be 175cm.
/// Ensure that scale also includes the base species height AND the user-defined height.
/// </summary>
/// <param name="scale"></param>
/// <returns></returns>
private static int GetMetricHeightFromScale(float scale = 1)
{
// cast as int because we don't care about decimal
return (int)Math.Max(scale * AVERAGE_HEIGHT_CM, 1); // can't be shorter than 1cm I guess
}
/// <summary>
/// DeltaV - Gets nicely formatted string that contains both metric and imperial measurements.
/// With a scale of 1, it should look like... 175cm (5' 9")
/// </summary>
/// <param name="scale"></param>
/// <returns></returns>
public static string GetMetricAndImperialDisplayFromScale(float scale = 1)
{
var metricHeight = GetMetricHeightFromScale(scale);
return $"{metricHeight}cm ({GetImperialDisplayLength(metricHeight)})";
}
public static string GetImperialDisplayLength(int lengthCm)
{
var heightIn = (int) Math.Round(lengthCm * 0.3937007874 /* cm to in*/);
return $"({heightIn / 12}'{heightIn % 12}'')";
var heightIn = (int)Math.Round(lengthCm * 0.3937007874 /* cm to in*/);
return $"{heightIn / 12}'{heightIn % 12}\"";
}
public static string GetImperialDisplayMass(int massKg)
{
var weightLbs = (int) Math.Round(massKg * 2.2046226218 /* kg to lbs */);
return $"({weightLbs} lbs)";
var weightLbs = (int)Math.Round(massKg * 2.2046226218 /* kg to lbs */);
return $"{weightLbs} lbs";
}
}

View File

@ -1,102 +0,0 @@
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

@ -1,23 +0,0 @@
<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

@ -1,99 +0,0 @@
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,73 @@
using System.Linq;
using Content.Shared._DV.Forensics;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Containers;
using Robust.Shared.Utility;
namespace Content.Client._DV.Forensics;
public sealed class DVItemSlotVisualsSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
private static readonly ResPath TextureRoot = new("/Textures");
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVItemSlotVisualsComponent, EntInsertedIntoContainerMessage>(OnInsertedIntoContainer);
SubscribeLocalEvent<DVItemSlotVisualsComponent, EntRemovedFromContainerMessage>(OnRemovedFromContainer);
}
private void RefreshVisuals(Entity<DVItemSlotVisualsComponent> ent)
{
var slot = _container.EnsureContainer<ContainerSlot>(ent, ent.Comp.ItemSlot);
if (slot.ContainedEntity is { } contained)
{
_sprite.CopySprite(contained, ent.Owner);
_sprite.AddLayer(ent.Owner, ent.Comp.FilledSprite);
}
else
{
var sprite = Comp<SpriteComponent>(ent);
var count = sprite.AllLayers.Count();
for (var i = count - 1; i >= 0; i--)
{
_sprite.RemoveLayer((ent, sprite), i);
}
if (ent.Comp.UnfilledSprite is not SpriteSpecifier.Rsi rsi)
{
throw new InvalidOperationException($"{ToPrettyString(ent)} has an unfilled sprite that's not an RSI");
}
if (!_resourceCache.TryGetResource<RSIResource>(TextureRoot / rsi.RsiPath, out var res))
{
throw new InvalidOperationException($"{ToPrettyString(ent)} has invalid RSI: {TextureRoot / rsi.RsiPath}");
}
_sprite.SetBaseRsi(ent.Owner, res.RSI);
_sprite.AddLayer(ent.Owner, ent.Comp.UnfilledSprite);
}
}
private void OnInsertedIntoContainer(Entity<DVItemSlotVisualsComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != ent.Comp.ItemSlot)
return;
RefreshVisuals(ent);
}
private void OnRemovedFromContainer(Entity<DVItemSlotVisualsComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (args.Container.ID != ent.Comp.ItemSlot)
return;
RefreshVisuals(ent);
}
}

View File

@ -0,0 +1,104 @@
using System.Numerics;
using Content.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Overlays;
/// <summary>
/// Makes darkness visible, and bright lights painfully visible
/// Tweakable. Algo is max((light*gain)^exp, lightFloor)
/// </summary>
public sealed class DarkVisionOverlay : Overlay
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
private readonly ProtoId<ShaderPrototype> _shaderProto = "DarkVision";
public float LightFloor = 0.5f;
public float LightGain = 2f;
public float LightExp = 1f;
private readonly ShaderInstance _copyShader;
private readonly ShaderInstance _remapShader;
private readonly OverlayResourceCache<CachedResources> _resources = new();
public DarkVisionOverlay()
{
IoCManager.InjectDependencies(this);
var proto = _prototype.Index<ShaderPrototype>(_shaderProto);
_remapShader = proto.InstanceUnique();
// With floor 0, gain 1, exp 1 the shader is an exact blend-mode-none copy.
_copyShader = proto.InstanceUnique();
_copyShader.SetParameter("lightFloor", 0f);
_copyShader.SetParameter("lightGain", 1f);
_copyShader.SetParameter("lightExp", 1f);
}
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.Viewport;
var worldHandle = args.WorldHandle;
if (viewport.Eye == null)
return;
var lightTarget = viewport.LightRenderTarget;
var res = _resources.GetForViewport(viewport, static _ => new CachedResources());
if (res.ScratchTarget?.Size != lightTarget.Size)
{
res.ScratchTarget?.Dispose();
res.ScratchTarget = _clyde.CreateLightRenderTarget(lightTarget.Size, "darkvision-scratch", depthStencil: false);
}
var bounds = args.WorldBounds;
var lightScale = lightTarget.Size / (Vector2) viewport.Size;
var scale = viewport.RenderScale / (Vector2.One / lightScale);
var localMatrix = lightTarget.GetWorldToLocalMatrix(viewport.Eye, scale);
// Copy the light buffer aside first: a texture can't be sampled while it is also the
// render target being drawn into.
worldHandle.RenderInRenderTarget(res.ScratchTarget, () =>
{
worldHandle.UseShader(_copyShader);
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRect(lightTarget.Texture, bounds);
worldHandle.UseShader(null);
}, Color.Black);
// Then write it back through the remap.
_remapShader.SetParameter("lightFloor", LightFloor);
_remapShader.SetParameter("lightGain", LightGain);
_remapShader.SetParameter("lightExp", LightExp);
worldHandle.RenderInRenderTarget(lightTarget, () =>
{
worldHandle.UseShader(_remapShader);
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRect(res.ScratchTarget.Texture, bounds);
worldHandle.UseShader(null);
}, null);
}
protected override void DisposeBehavior()
{
_resources.Dispose();
base.DisposeBehavior();
}
private sealed class CachedResources : IDisposable
{
public IRenderTexture? ScratchTarget;
public void Dispose()
{
ScratchTarget?.Dispose();
}
}
}

View File

@ -0,0 +1,56 @@
using Content.Shared._DV.Overlays.Components;
using Robust.Client.Graphics;
using Robust.Shared.Player;
namespace Content.Client._DV.Overlays;
public sealed class DarkVisionSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
private DarkVisionOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DarkVisionComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DarkVisionComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<DarkVisionComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DarkVisionComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnInit(Entity<DarkVisionComponent> ent, ref ComponentInit args)
{
if (ent.Owner == _playerMan.LocalEntity)
EnableOverlay(ent.Comp);
}
private void OnShutdown(Entity<DarkVisionComponent> ent, ref ComponentShutdown args)
{
if (ent.Owner == _playerMan.LocalEntity)
_overlayMan.RemoveOverlay(_overlay);
}
private void OnPlayerAttached(Entity<DarkVisionComponent> ent, ref LocalPlayerAttachedEvent args)
{
EnableOverlay(ent.Comp);
}
private void OnPlayerDetached(Entity<DarkVisionComponent> ent, ref LocalPlayerDetachedEvent args)
{
_overlayMan.RemoveOverlay(_overlay);
}
private void EnableOverlay(DarkVisionComponent comp)
{
_overlay.LightFloor = comp.LightFloor;
_overlay.LightGain = comp.LightGain;
_overlay.LightExp = comp.LightExp;
if (!_overlayMan.HasOverlay<DarkVisionOverlay>())
_overlayMan.AddOverlay(_overlay);
}
}

View File

@ -0,0 +1,71 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
using Robust.Shared.Utility;
namespace Content.Client._Floof.OfferItem;
public sealed class OfferItemIndicatorsOverlay : Overlay
{
private readonly IInputManager _inputManager;
private readonly IEntityManager _entMan;
private readonly IEyeManager _eye;
private readonly OfferItemSystem _offer;
private readonly Texture _sight;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly Color _mainColor = Color.White.WithAlpha(0.3f);
private readonly Color _strokeColor = Color.Black.WithAlpha(0.5f);
private readonly float _scale = 0.6f; // 1 is a little big
public OfferItemIndicatorsOverlay(IInputManager input, IEntityManager entMan,
IEyeManager eye, OfferItemSystem offerSys)
{
_inputManager = input;
_entMan = entMan;
_eye = eye;
_offer = offerSys;
var spriteSys = _entMan.EntitySysManager.GetEntitySystem<SpriteSystem>();
_sight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new("/Textures/_Floof/Interface/Misc/give_item.rsi"), "give_item"));
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_offer.IsInOfferMode())
return false;
return base.BeforeDraw(in args);
}
protected override void Draw(in OverlayDrawArgs args)
{
var mouseScreenPosition = _inputManager.MouseScreenPosition;
var mousePosMap = _eye.PixelToMap(mouseScreenPosition);
if (mousePosMap.MapId != args.MapId)
return;
var mousePos = mouseScreenPosition.Position;
var uiScale = (args.ViewportControl as Control)?.UIScale ?? 1f;
var limitedScale = uiScale > 1.25f ? 1.25f : uiScale;
DrawSight(_sight, args.ScreenHandle, mousePos, limitedScale * _scale);
}
private void DrawSight(Texture sight, DrawingHandleScreen screen, Vector2 centerPos, float scale)
{
var sightSize = sight.Size * scale;
var expandedSize = sightSize + new Vector2(7f, 7f);
screen.DrawTextureRect(sight,
UIBox2.FromDimensions(centerPos - sightSize * 0.5f, sightSize), _strokeColor);
screen.DrawTextureRect(sight,
UIBox2.FromDimensions(centerPos - expandedSize * 0.5f, expandedSize), _mainColor);
}
}

View File

@ -0,0 +1,39 @@
using Content.Shared._Floof.OfferItem;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
namespace Content.Client._Floof.OfferItem;
public sealed class OfferItemSystem : SharedOfferItemSystem
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IEyeManager _eye = default!;
public override void Initialize()
{
base.Initialize();
_overlayManager.AddOverlay(new OfferItemIndicatorsOverlay(
_inputManager,
EntityManager,
_eye,
this));
}
public override void Shutdown()
{
_overlayManager.RemoveOverlay<OfferItemIndicatorsOverlay>();
base.Shutdown();
}
public bool IsInOfferMode()
{
var entity = _playerManager.LocalEntity;
if (entity == null)
return false;
return IsInOfferMode(entity.Value);
}
}

View File

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Access.Systems; // DeltaV
using Content.Server.Cargo.Components;
using Content.Server.NameIdentifier;
using Content.Shared.Access.Components;
@ -20,6 +21,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Shared._DV.Cargo.Components; // DeltaV: Bounty claim messages
namespace Content.Server.Cargo.Systems;
@ -28,6 +30,7 @@ public sealed partial class CargoSystem
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
[Dependency] private readonly IdCardSystem _idCard = default!; // DeltaV
private static readonly ProtoId<NameIdentifierGroupPrototype> BountyNameIdentifierGroup = "Bounty";
@ -40,6 +43,8 @@ public sealed partial class CargoSystem
SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
SubscribeLocalEvent<CargoBountyConsoleComponent, BountySkipMessage>(OnSkipBountyMessage);
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyClaimedMessage>(OnBountyClaimedMessage); // DeltaV
SubscribeLocalEvent<CargoBountyConsoleComponent, BountySetStatusMessage>(OnSetBountyStatusMessage); // DeltaV
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
@ -111,6 +116,72 @@ public sealed partial class CargoSystem
_audio.PlayPvs(component.SkipSound, uid);
}
// Begin DeltaV bounty claim system
private void OnSetBountyStatusMessage(Entity<CargoBountyConsoleComponent> ent, ref BountySetStatusMessage args)
{
if (_station.GetOwningStation(ent.Owner) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDbComp))
return;
for (var i = 0; i < bountyDbComp.Bounties.Count; i++)
{
var bounty = bountyDbComp.Bounties[i];
if (bounty.Id != args.BountyId)
continue;
var newData = new CargoBountyData
{
Id = bounty.Id,
Bounty = bounty.Bounty,
ClaimedBy = bounty.ClaimedBy,
Status = (CargoBountyStatus)args.Status,
};
bountyDbComp.Bounties[i] = newData;
}
var untilNextSkip = bountyDbComp.NextSkipTime - Timing.CurTime;
_uiSystem.SetUiState(ent.Owner, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDbComp.Bounties, bountyDbComp.History, untilNextSkip));
}
private void OnBountyClaimedMessage(Entity<CargoBountyConsoleComponent> ent, ref BountyClaimedMessage args)
{
if (_station.GetOwningStation(ent.Owner) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDbComp))
return;
for (var i = 0; i < bountyDbComp.Bounties.Count; i++)
{
var bounty = bountyDbComp.Bounties[i];
if (bounty.Id != args.BountyId)
continue;
string name;
if (_idCard.TryFindIdCard(args.Actor, out var idCard) && idCard.Comp.FullName != null)
{
name = idCard.Comp.FullName;
}
else
{
name = Loc.GetString("bounty-console-claimed-by-unknown");
}
var newData = new CargoBountyData
{
Id = bounty.Id,
Bounty = bounty.Bounty,
ClaimedBy = name.Equals(bounty.ClaimedBy) ? string.Empty : name,
Status = bounty.Status,
};
bountyDbComp.Bounties[i] = newData;
}
var untilNextSkip = bountyDbComp.NextSkipTime - Timing.CurTime;
_uiSystem.SetUiState(ent.Owner, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDbComp.Bounties, bountyDbComp.History, untilNextSkip));
}
// End DeltaV bounty claim system
public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
{
if (!Resolve(uid, ref paper, ref label) || !_protoMan.Resolve<CargoBountyPrototype>(bounty.Bounty, out var prototype))

View File

@ -23,6 +23,7 @@ using Robust.Shared.Timing;
using System.Linq;
using Content.Shared.EntityEffects.Effects.Solution;
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
using Content.Server.Body.Components; // Delta V
namespace Content.Server.Fluids.EntitySystems;
@ -267,7 +268,8 @@ public sealed class SmokeSystem : EntitySystem
if (!_solutionContainerSystem.ResolveSolution(entity, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution) || bloodSolution.AvailableVolume <= 0)
return;
var blockIngestion = _internals.AreInternalsWorking(entity);
var blockIngestion = _internals.AreInternalsWorking(entity)
|| !HasComp<RespiratorComponent>(entity); // Starlight - Shadekin does not breathe and "AreInternalsWorking" does not check for that
var cloneSolution = solution.Clone();
var availableTransfer = FixedPoint2.Min(cloneSolution.Volume, component.TransferRate);

View File

@ -41,6 +41,7 @@ using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Content.Server.Mobs; // Starlight
namespace Content.Server.Ghost
{
@ -619,7 +620,15 @@ namespace Content.Server.Ghost
_damageable.GetTotalDamage((playerEntity.Value, damageable));
}
DamageSpecifier damage = new(_prototypeManager.Index(AsphyxiationDamageType), dealtDamage);
// Starlight - Start
//DamageSpecifier damage = new(_prototypeManager.Index(AsphyxiationDamageType), dealtDamage);
var damageType = _prototypeManager.Index(AsphyxiationDamageType);
if (TryComp<DeathgaspComponent>(playerEntity, out var deathgasp))
damageType = _prototypeManager.Index(deathgasp.DamageType);
DamageSpecifier damage = new(damageType, dealtDamage);
// Starlight - End
_damageable.ChangeDamage(playerEntity.Value, damage, true);
}

View File

@ -23,6 +23,7 @@ using Content.Server.Body.Systems;
// Begin DeltaV
using Content.Server._DV.MedicalRecords;
using Content.Shared._DV.MedicalRecords;
using Content.Shared.Chemistry.Components;
// End DeltaV
namespace Content.Server.Medical;
@ -269,9 +270,11 @@ public sealed partial class HealthAnalyzerSystem : EntitySystem // DeltaV - Made
var bleeding = false;
var unrevivable = false;
Solution? bloodSolution = null; // DeltaV - Health Analyzer Plus
if (TryComp<BloodstreamComponent>(entity, out var bloodstream) &&
_solutionContainerSystem.ResolveSolution(entity, bloodstream.BloodSolutionName,
ref bloodstream.BloodSolution, out var bloodSolution))
ref bloodstream.BloodSolution, out bloodSolution)) // DeltaV - Health Analyzer Plus
{
bloodAmount = _bloodstreamSystem.GetBloodLevel(entity);
bleeding = bloodstream.BleedAmount > 0;
@ -287,6 +290,7 @@ public sealed partial class HealthAnalyzerSystem : EntitySystem // DeltaV - Made
null,
bleeding,
unrevivable,
bloodSolution, // DeltaV - Health Analyzer Plus
_medicalRecords.GetMedicalRecords(entity) // DeltaV - Medical Records
);
}

View File

@ -1,4 +1,6 @@
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chat.Prototypes; // Starlight
using Content.Shared.Damage.Prototypes; // Starlight
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Mobs;
@ -21,4 +23,10 @@ public sealed partial class DeathgaspComponent : Component
/// </summary>
[DataField]
public bool NeedsCritical = true;
/// <summary>
/// Starlight - The damage that is taken when succumbing
/// </summary>
[DataField]
public ProtoId<DamageTypePrototype> DamageType = "Asphyxiation";
}

View File

@ -55,9 +55,13 @@ public sealed class PseudoItemSystem : SharedPseudoItemSystem
protected override void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args)
{
// Floof - changed this a bit to actually start a do-after
// Try to pick the entity up instead first
if (args.User != args.Item && _carrying.TryCarry(args.User, uid))
if (args.User != args.Item
&& TryComp<CarriableComponent>(uid, out var carriable)
&& _carrying.CanCarry(args.User, (uid, carriable)))
{
_carrying.StartCarryDoAfter(args.User, (uid, carriable));
args.Cancel();
return;
}

View File

@ -209,20 +209,24 @@ namespace Content.Server.Psionics.Glimmer
private void OnDestroyed(EntityUid uid, SharedGlimmerReactiveComponent component, DestructionEventArgs args)
{
Spawn("MaterialBluespace1", Transform(uid).Coordinates);
var proberCoords = Transform(uid).Coordinates;
var tier = _glimmerSystem.GetGlimmerTier();
if (tier < GlimmerTier.High)
return;
var totalIntensity = (float) (_glimmerSystem.Glimmer * 2);
var slope = (float) (11 - _glimmerSystem.Glimmer / 100);
var maxIntensity = 20;
var explosionMultiplier = 2;
if (_glimmerSystem.GetGlimmerTier() == GlimmerTier.Critical) // YOU DONE FUCKED UP
explosionMultiplier = 3;
var removed = (float) _glimmerSystem.Glimmer * _random.NextFloat(0.06f, 0.08f);
_glimmerSystem.Glimmer -= (int) removed;
var totalIntensity = (float)(_glimmerSystem.Glimmer * explosionMultiplier);
var slope = (float)(11 - _glimmerSystem.Glimmer / 100);
var maxIntensity = 75; // Same as syndicate bomb
var removed = _glimmerSystem.Glimmer * _random.NextFloat(0.06f, 0.08f);
_glimmerSystem.Glimmer -= (int)removed;
BeamRandomNearProber(uid, _glimmerSystem.Glimmer / 350, _glimmerSystem.Glimmer / 50);
_explosionSystem.QueueExplosion(uid, "Default", totalIntensity, slope, maxIntensity);
_explosionSystem.QueueExplosion(uid, "Default", totalIntensity, slope, maxIntensity, addLog: true);
Spawn("MaterialBluespace1", proberCoords); // Congrats on your bluespace!
}
private void OnUnanchorAttempt(EntityUid uid, SharedGlimmerReactiveComponent component, UnanchorAttemptEvent args)
@ -230,7 +234,7 @@ namespace Content.Server.Psionics.Glimmer
if (component.Locked)
{
_sharedAudioSystem.PlayPvs(component.ShockNoises, args.User);
_electrocutionSystem.TryDoElectrocution(args.User, null, _glimmerSystem.Glimmer / 200, TimeSpan.FromSeconds((float) _glimmerSystem.Glimmer / 100), false);
_electrocutionSystem.TryDoElectrocution(args.User, uid, _glimmerSystem.Glimmer / 200, TimeSpan.FromSeconds((float) _glimmerSystem.Glimmer / 100), false);
args.Cancel();
}
}

View File

@ -184,7 +184,8 @@ public sealed class OracleSystem : EntitySystem
while (i != 0)
{
Spawn("MaterialBluespace1", Transform(user).Coordinates);
var entityToSpawn = _random.Next(0, 2) == 0 ? "MaterialBluespace1" : "CrystalNormality";
Spawn(entityToSpawn, Transform(user).Coordinates);
i--;
}

View File

@ -125,12 +125,12 @@ public sealed class ProjectileSystem : SharedProjectileSystem
RaiseLocalEvent(projectile, ref pierceEv);
// If the object won't be destroyed, it "tanks" the penetration hit.
if (damage.GetTotal() < damageRequired)
if (damage.GetTotal() < damageRequired && !pierceEv.Pierced) // DeltaV - Addition of the NT-3
{
return false;
}
if (!projectile.Comp.ProjectileSpent)
if (!projectile.Comp.ProjectileSpent && !pierceEv.Pierced) // DeltaV - Addition of the NT-3
{
projectile.Comp.PenetrationAmount += damageRequired;
// The projectile has dealt enough damage to be spent.
@ -139,7 +139,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
return false;
}
if (projectile.Comp.ProjectileSpent && pierceEv.Pierced) // DeltaV - Addition of the NT-3
if (projectile.Comp.ProjectileSpent)
{
return true;
}

View File

@ -1,43 +0,0 @@
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

@ -1,4 +1,5 @@
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.Projectiles.Components;
@ -21,10 +22,14 @@ public sealed partial class PiercingProjectileComponent : Component
public float PierceCounter;
/// <summary>
/// The tag that will cause the piercing bullet to increment it's <see cref="PierceCounter"/>.
/// The whitelist for checking what increments the <see cref="PierceCounter"/>.
/// </summary>
/// <example>
/// If this has the tag "Wall" in it, any entity with the tag "Wall" will increment <see cref="PierceCounter"/>
/// upon being hit.
/// </example>
[DataField]
public List<ProtoId<TagPrototype>> PierceBlockTag = ["Wall", "Window"];
public EntityWhitelist PierceCounterWhitelist;
/// <summary>
/// The number of entities it is allowed to pierce before being deleted.

View File

@ -3,7 +3,7 @@ using Content.Shared.FixedPoint;
namespace Content.Server._DV.Projectiles.Events;
/// <summary>
/// Raised when a piercing projectile hits an entity that doesn't follow upstream piercing rules.
/// Raised when a piercing projectile that doesn't follow upstream piercing rules hits an entity.
/// </summary>
[ByRefEvent]
public record struct ProjectilePierceEvent(EntityUid Target, FixedPoint2 RequiredDamage, bool Pierced = false);

View File

@ -1,29 +1,33 @@
using Content.Server._DV.Projectiles.Components;
using Content.Server._DV.Projectiles.Events;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
namespace Content.Server._DV.Projectiles.Systems;
public sealed class PiercingProjectileSystem : EntitySystem
{
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
// Mobs return a required Damage amount of Float.MaxValue. Therefore, we need to check for absurdly high values.
private readonly int _indestructibleNumber = 20000000;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PiercingProjectileComponent, ProjectilePierceEvent>(OnPierce);
}
private void OnPierce(Entity<PiercingProjectileComponent> bullet, ref ProjectilePierceEvent args)
{
// If the target doesn't have any tags to stop the bullet from piercing, it's automatically true.
if (!_tagSystem.HasAnyTag(args.Target, bullet.Comp.PierceBlockTag))
if (_whitelist.IsWhitelistFail(bullet.Comp.PierceCounterWhitelist, args.Target))
{
args.Pierced = true;
return;
}
// If it does have the tag to stop it and enough health to count as "strongly armored", it'll block the bullet.
if (bullet.Comp.HealthThreshold < args.RequiredDamage)
if (bullet.Comp.HealthThreshold < args.RequiredDamage && args.RequiredDamage < _indestructibleNumber)
return;
if (bullet.Comp.Direction == null) // Get the direction of the bullet to determine which walls count.

View File

@ -109,8 +109,10 @@ public sealed class FugitiveRule : StationEventSystem<FugitiveRuleComponent>
var report = new FormattedMessage();
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-title"));
report.PushNewline();
report.PushNewline();
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-first-line"));
report.PushNewline();
report.PushNewline();
if (!TryComp<HumanoidProfileComponent>(uid, out var humanoid))
{
@ -121,22 +123,28 @@ public sealed class FugitiveRule : StationEventSystem<FugitiveRuleComponent>
var species = PrototypeManager.Index(humanoid.Species);
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-morphotype", ("species", Loc.GetString(species.Name))));
report.PushNewline();
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-age", ("age", humanoid.Age)));
report.PushNewline();
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-sex", ("sex", humanoid.Sex)));
report.PushNewline();
if (TryComp<PhysicsComponent>(uid, out var physics))
{
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-weight", ("weight", Math.Round(physics.FixturesMass))));
report.PushNewline();
}
// add a random identifying quality that officers can use to track them down
report.AddMarkupOrThrow(RobustRandom.Next(0, 2) switch
{
0 => Loc.GetString("fugitive-report-detail-dna", ("dna", GetDNA(uid))),
_ => Loc.GetString("fugitive-report-detail-prints", ("prints", GetPrints(uid)))
});
report.PushNewline();
report.PushNewline();
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-crimes-header"));
report.PushNewline();
// generate some random crimes to avoid this situation
// "officer what are my charges?"
// "uh i dunno a piece of paper said to arrest you thats it"
@ -172,6 +180,7 @@ public sealed class FugitiveRule : StationEventSystem<FugitiveRuleComponent>
{
var count = RobustRandom.Next(rule.MinCounts, rule.MaxCounts + 1);
report.AddMarkupOrThrow(Loc.GetString("fugitive-report-crime", ("crime", Loc.GetString(crime)), ("count", count)));
report.PushNewline();
}
}
}

View File

@ -0,0 +1,163 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Text.RegularExpressions;
using Content.Server.Administration;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Maths;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Utility;
namespace Content.Server._DV.Toolshed;
/// <summary>
/// An extended version of the engine's <c>do</c> command. Runs a console command once per piped value,
/// substituting <c>$NAME</c> tokens in the command string first.
/// </summary>
/// <remarks>
/// On top of the built-in tokens that <c>do</c> supports (<c>$ID</c>, <c>$PID</c>, <c>$WX</c>, <c>$WY</c>,
/// <c>$LX</c>, <c>$LY</c>, <c>$SELF</c>), any other <c>$name</c> is resolved as a toolshed variable and
/// converted to a string. Tokens that don't resolve to anything are left untouched.
///
/// Where the command actually runs is explicit:
/// <list type="bullet">
/// <item><c>doext:client</c> sends it back down to the calling user's own client to run locally. This is what
/// you want for client-side commands like <c>exec</c>, which resolve paths against the client's user data.</item>
/// <item><c>doext:server</c> runs it server-side as the calling session, like <c>do</c> does.</item>
/// </list>
/// </remarks>
/// <example><code>
/// self doext:client "exec /Script_$var.txt"
/// i 5 => $count; self doext:server "somecommand $count"
/// </code></example>
[ToolshedCommand(Name = "doext"), AdminCommand(AdminFlags.Debug)]
public sealed class DoExtCommand : ToolshedCommand
{
/// <summary>
/// Matches a <c>$name</c> token. Names use the same character set as toolshed variable names, so this
/// matches whole identifiers - <c>$IDLE</c> resolves as "IDLE" rather than being mangled into <c>$ID</c>.
/// </summary>
private static readonly Regex TokenRegex = new(@"\$(\w+)", RegexOptions.Compiled);
[Dependency] private readonly IConsoleHost _console = default!;
private SharedTransformSystem? _xform;
/// <summary>
/// Sends the command back down to the calling user's client, which runs it locally.
/// </summary>
[CommandImplementation("client"), TakesPipedTypeAsGeneric]
public IEnumerable<T> Client<T>(IInvocationContext ctx, [PipedArgument] IEnumerable<T> input, string command)
{
// No session means there's no client to hand this back to - e.g. the server console.
if (ctx.Session is not { } session)
{
ctx.ReportError(new NoClientSessionError());
yield break;
}
foreach (var value in input)
{
_console.RemoteExecuteCommand(session, Substitute(command, value, ctx));
yield return value;
}
}
/// <summary>
/// Runs the command on the server as the calling session.
/// </summary>
[CommandImplementation("server"), TakesPipedTypeAsGeneric]
public IEnumerable<T> Server<T>(IInvocationContext ctx, [PipedArgument] IEnumerable<T> input, string command)
{
foreach (var value in input)
{
_console.ExecuteCommand(ctx.Session, Substitute(command, value, ctx));
yield return value;
}
}
private string Substitute<T>(string command, T value, IInvocationContext ctx)
{
return TokenRegex.Replace(command, match =>
{
var name = match.Groups[1].Value;
if (TryGetBuiltin(name, value, ctx, out var builtin))
return builtin;
// Anything else is looked up as a toolshed variable and implicitly stringified.
if (ctx.ReadVar(name) is { } variable)
return Stringify(variable);
// Unresolved, so leave the token alone rather than silently blanking it.
return match.Value;
});
}
private bool TryGetBuiltin<T>(string name, T value, IInvocationContext ctx, [NotNullWhen(true)] out string? result)
{
switch (name)
{
case "SELF":
result = Stringify(value);
return true;
case "PID":
result = (ctx.Session?.AttachedEntity ?? EntityUid.Invalid).ToString();
return true;
}
// The remaining tokens are all positional, so they only apply when piping entities.
if (value is not EntityUid uid)
{
result = null;
return false;
}
switch (name)
{
case "ID":
result = uid.ToString();
return true;
case "WX":
result = Number(WorldPosition(uid).X);
return true;
case "WY":
result = Number(WorldPosition(uid).Y);
return true;
case "LX":
result = Number(Transform(uid).Coordinates.X);
return true;
case "LY":
result = Number(Transform(uid).Coordinates.Y);
return true;
}
result = null;
return false;
}
private Vector2 WorldPosition(EntityUid uid)
{
_xform ??= GetSys<SharedTransformSystem>();
return _xform.GetWorldPosition(uid);
}
private static string Number(float value) => value.ToString(CultureInfo.InvariantCulture);
private static string Stringify(object? value) => value?.ToString() ?? string.Empty;
}
public sealed class NoClientSessionError : IConError
{
public FormattedMessage DescribeInner()
{
return FormattedMessage.FromUnformatted("There is no client to run this on. doext:client must be run by a player, not the server console.");
}
public string? Expression { get; set; }
public Vector2i? IssueSpan { get; set; }
public StackTrace? Trace { get; set; }
}

View File

@ -0,0 +1,136 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Content.Server.Administration;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Maths;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server._DV.Toolshed;
/// <summary>
/// Reads the value of a single field or property off of the piped input, by name.
/// Member names are resolved the same way <c>vvread</c> does: the member must be exposed
/// via <c>[ViewVariables]</c>, <c>[DataField]</c>, or <c>[IncludeDataField]</c>.
/// </summary>
/// <example><code>ent 12345 . Name</code></example>
[ToolshedCommand(Name = "."), AdminCommand(AdminFlags.Debug)]
public sealed class FieldCommand : ToolshedCommand
{
[CommandImplementation]
public object? Field([PipedArgument] object? value, [CommandArgument(typeof(FieldNameParser))] string field, IInvocationContext ctx)
{
if (value == null)
{
ctx.ReportError(new NullInputFieldError());
return null;
}
var type = value.GetType();
var member = GetSingleMember(type, field);
// Restrict to members that vvread would let you read, so this can't be used to peek at arbitrary internals.
if (member == null || !ViewVariablesUtility.TryGetViewVariablesAccess(member, out _))
{
ctx.ReportError(new NoSuchFieldError(type, field));
return null;
}
return member switch
{
FieldInfo f => f.GetValue(value),
PropertyInfo p => p.GetValue(value),
_ => null,
};
}
/// <summary>
/// Finds the field or property with the given name, mirroring the resolution used by <c>vvread</c>.
/// </summary>
private static MemberInfo? GetSingleMember(Type type, string member)
{
var members = type
.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.Name == member && m is FieldInfo or PropertyInfo)
.ToArray();
if (members.Length == 0)
return null;
// In case there's member hiding going on, grab the one declared by the type of the object by default.
return members.Length > 1
? members.FirstOrDefault(m => m.DeclaringType == type) ?? members[0]
: members[0];
}
}
/// <summary>
/// Parses a field name as a bare identifier (letters, digits, underscore) so that the <c>.</c> command doesn't
/// require the name to be wrapped in quotes like the default <see cref="string"/> parser does.
/// </summary>
public sealed class FieldNameParser : CustomTypeParser<string>
{
public override bool TryParse(ParserContext ctx, [NotNullWhen(true)] out string? result)
{
ctx.ConsumeWhitespace();
result = ctx.GetWord(ParserContext.IsToken);
if (result != null)
return true;
if (ctx.PeekRune() is null)
ctx.Error = new OutOfInputError();
else
ctx.Error = new InvalidFieldNameError();
ctx.Error.Contextualize(ctx.Input, (ctx.Index, ctx.Index + 1));
return false;
}
public override CompletionResult? TryAutocomplete(ParserContext ctx, CommandArgument? arg)
{
return CompletionResult.FromHint(GetArgHint(arg));
}
}
public sealed class InvalidFieldNameError : IConError
{
public FormattedMessage DescribeInner()
{
return FormattedMessage.FromUnformatted("Expected a field name (letters, digits, or underscores).");
}
public string? Expression { get; set; }
public Vector2i? IssueSpan { get; set; }
public StackTrace? Trace { get; set; }
}
public sealed class NullInputFieldError : IConError
{
public FormattedMessage DescribeInner()
{
return FormattedMessage.FromUnformatted("Cannot read a field off of a null input.");
}
public string? Expression { get; set; }
public Vector2i? IssueSpan { get; set; }
public StackTrace? Trace { get; set; }
}
public sealed class NoSuchFieldError(Type type, string field) : IConError
{
public FormattedMessage DescribeInner()
{
return FormattedMessage.FromUnformatted($"Type {type.Name} has no readable field or property named '{field}'. It must be exposed via [ViewVariables], [DataField], or [IncludeDataField].");
}
public string? Expression { get; set; }
public Vector2i? IssueSpan { get; set; }
public StackTrace? Trace { get; set; }
}

View File

@ -0,0 +1,5 @@
using Content.Shared._Floof.OfferItem;
namespace Content.Server._Floof.OfferItem;
public sealed partial class OfferItemSystem : SharedOfferItemSystem;

View File

@ -0,0 +1,293 @@
using Content.Shared.Humanoid;
using Content.Shared.Alert;
using Robust.Server.GameObjects;
using Content.Shared.Examine;
using Robust.Server.Containers;
using Content.Shared._Starlight;
using Content.Shared.Damage.Components;
using Content.Shared.Mobs;
using Content.Shared.Movement.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Damage;
using Content.Server.Chat.Managers;
using Content.Shared.Damage.Systems;
using Content.Shared._Goobstation.Overlays;
using Robust.Shared.Timing;
using Content.Server.Body.Components;
using System.Linq;
using Content.Shared._Goobstation.Flashbang;
using Content.Shared._Starlight.Flash.Components;
using Content.Shared.Body.Components;
using Content.Shared.Flash; // Delta V - Flash Work
using Content.Shared._DV.Body.Components; // Delta V - Remove various comps related to breathing
namespace Content.Server._Starlight;
public sealed class ShadekinSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MovementSpeedModifierSystem _speed = default!;
[Dependency] private readonly SharedFlashSystem _flashSystem = default!;
private sealed class LightCone
{
public float Direction { get; set; }
public float InnerWidth { get; set; }
public float OuterWidth { get; set; }
}
private readonly Dictionary<string, List<LightCone>> lightMasks = new()
{
["/Textures/Effects/LightMasks/cone.png"] = new List<LightCone>
{
new LightCone { Direction = 0, InnerWidth = 30, OuterWidth = 60 }
},
["/Textures/Effects/LightMasks/double_cone.png"] = new List<LightCone>
{
new LightCone { Direction = 0, InnerWidth = 30, OuterWidth = 60 },
new LightCone { Direction = 180, InnerWidth = 30, OuterWidth = 60 }
}
};
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ShadekinComponent, ComponentStartup>(OnInit);
SubscribeLocalEvent<ShadekinComponent, EyeColorInitEvent>(OnEyeColorChange);
SubscribeLocalEvent<ShadekinComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
SubscribeLocalEvent<ShadekinComponent, AfterFlashedEvent>(OnShadekinFlashed); // Delta V - Prevent Chain Flashing
SubscribeLocalEvent<ShadekinComponent, FlashDurationMultiplierEvent>(GetFlashModifier); // Delta V - Flash Modifier to Shadekin
}
private void OnInit(EntityUid uid, ShadekinComponent component, ComponentStartup args)
{
UpdateAlert(uid, component, (short)component.CurrentState);
RemComp<InternalsComponent>(uid);
RemComp<RespiratorComponent>(uid);
RemComp<AffectedByCPRComponent>(uid); // No lungs = no CPR
}
private void OnEyeColorChange(EntityUid uid, ShadekinComponent component, EyeColorInitEvent args)
{
if (!TryComp<HumanoidProfileComponent>(uid, out var humanoid))
return;
// humanoid.EyeColor = Color.Black;
Dirty(uid, humanoid);
}
public void UpdateAlert(EntityUid uid, ShadekinComponent component, short state)
{
_alerts.ShowAlert(uid, component.ShadekinAlert, state);
}
private Angle GetAngle(EntityUid lightUid, SharedPointLightComponent lightComp, EntityUid targetUid)
{
var (lightPos, lightRot) = _transform.GetWorldPositionRotation(lightUid);
lightPos += lightRot.RotateVec(lightComp.Offset);
var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetUid);
var mapDiff = targetPos - lightPos;
var oppositeMapDiff = (-lightRot).RotateVec(mapDiff);
var angle = oppositeMapDiff.ToWorldAngle();
if (angle == double.NaN && _transform.ContainsEntity(targetUid, lightUid) || _transform.ContainsEntity(lightUid, targetUid))
{
angle = 0f;
}
return angle;
}
/// <summary>
/// Return an illumination float value with is how many "energy" of light is hitting our ent.
/// WARNING: This function might be expensive, Avoid calling it too much and CACHE THE RESULT!
/// </summary>
/// <param name="uid"></param>
/// <returns></returns>
public float GetLightExposure(EntityUid uid)
{
var illumination = 0f;
var lightQuery = _lookup.GetEntitiesInRange<PointLightComponent>(Transform(uid).Coordinates, 20, LookupFlags.Uncontained);
foreach (var light in lightQuery)
{
if (!light.Comp.Enabled
|| light.Comp.Radius < 1
|| light.Comp.Energy <= 0)
continue;
var (lightPos, lightRot) = _transform.GetWorldPositionRotation(light);
lightPos += lightRot.RotateVec(light.Comp.Offset);
if (!_examine.InRangeUnOccluded(light, uid, light.Comp.Radius, null))
continue;
Transform(uid).Coordinates.TryDistance(EntityManager, Transform(light).Coordinates, out var dist);
var denom = dist / light.Comp.Radius;
var attenuation = 1 - (denom * denom);
var calculatedLight = 0f;
if (light.Comp.MaskPath is not null)
{
var angleToTarget = GetAngle(light, light.Comp, uid);
foreach (var cone in lightMasks[light.Comp.MaskPath])
{
var coneLight = 0f;
var angleAttenuation = (float)Math.Min((float)Math.Max(cone.OuterWidth - angleToTarget, 0f), cone.InnerWidth) / cone.OuterWidth;
if (angleToTarget.Degrees - cone.Direction > cone.OuterWidth)
continue;
else if (angleToTarget.Degrees - cone.Direction > cone.InnerWidth
&& angleToTarget.Degrees - cone.Direction < cone.OuterWidth)
coneLight = light.Comp.Energy * attenuation * attenuation * angleAttenuation;
else
coneLight = light.Comp.Energy * attenuation * attenuation;
calculatedLight = Math.Max(calculatedLight, coneLight);
}
}
else
calculatedLight = light.Comp.Energy * attenuation * attenuation;
illumination += calculatedLight; //Math.Max(illumination, calculatedLight);
}
return illumination;
}
private void SetPassiveBuff(EntityUid uid, ShadekinState state)
{
if (!TryComp<PassiveDamageComponent>(uid, out var passive))
return;
if (state == ShadekinState.Extreme || state == ShadekinState.Annoying || state == ShadekinState.High)
{
// passive.DamageCap = 1;
}
else if (state == ShadekinState.Low)
{
// passive.DamageCap = 20;
passive.AllowedStates.Clear();
passive.AllowedStates.Add(MobState.Alive);
passive.Interval = 1f;
}
else if (state != ShadekinState.Dark)
{
// passive.DamageCap = 0;
passive.AllowedStates.Clear();
passive.AllowedStates.Add(MobState.Alive);
passive.AllowedStates.Add(MobState.Critical);
passive.AllowedStates.Add(MobState.Dead);
passive.Interval = 0.5f;
}
}
private void ToggleNightVision(EntityUid uid, ShadekinState state)
{
if (state == ShadekinState.Dark)
{
var nightVisionComponent = EnsureComp<NightVisionComponent>(uid);
nightVisionComponent.Color = Color.FromHex("#808080"); // Delta V - Change Night Vision Color
}
else
{
if (TryComp<NightVisionComponent>(uid, out var nightVision) && nightVision.IsActive)
_flashSystem.Flash(uid, uid, uid, TimeSpan.FromSeconds(0.5 * (int)state), 0.5f);
RemComp<NightVisionComponent>(uid);
}
}
private void ApplyLightDamage(EntityUid uid, float dmg)
{
var damage = new DamageSpecifier();
damage.DamageDict.Add("Heat", dmg);
_damageable.TryChangeDamage(uid, damage, true, false);
}
private void OnRefreshMovementSpeedModifiers(EntityUid uid, ShadekinComponent component, RefreshMovementSpeedModifiersEvent args)
{
if (component.CurrentState == ShadekinState.Low || component.CurrentState == ShadekinState.Annoying ||
component.CurrentState == ShadekinState.Dark || component.CurrentState == ShadekinState.Invalid)
return;
if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
return;
var sprintDif = movement.BaseWalkSpeed / movement.BaseSprintSpeed;
args.ModifySpeed(1f, sprintDif);
}
private ShadekinState GetStateByThreshold(ShadekinComponent component, float lightExposure)
{
var returnState = ShadekinState.Dark;
foreach (var (threshold, shadekinState) in component.Thresholds.Reverse())
{
if (threshold <= lightExposure)
{
returnState = shadekinState;
break;
}
}
return returnState;
}
// Delta V - Begin Shadekin Flash Changes
private void GetFlashModifier(EntityUid uid, ShadekinComponent comp, FlashDurationMultiplierEvent args)
{
if (!TryComp<FlashModifierComponent>(uid, out var flashModifier))
return;
args.Multiplier = flashModifier.Modifier;
}
private void OnShadekinFlashed(EntityUid uid, ShadekinComponent comp, AfterFlashedEvent ev)
{
RemComp<NightVisionComponent>(uid);
}
// Delta V - End
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ShadekinComponent>();
while (query.MoveNext(out var uid, out var component))
{
if (_timing.CurTime < component.NextUpdate)
continue;
component.NextUpdate = _timing.CurTime + component.UpdateCooldown;
var lightExposure = 0f;
if (!_container.IsEntityInContainer(uid))
lightExposure = GetLightExposure(uid);
component.CurrentState = GetStateByThreshold(component, lightExposure);
UpdateAlert(uid, component, (short)component.CurrentState);
SetPassiveBuff(uid, component.CurrentState);
ToggleNightVision(uid, component.CurrentState);
_speed.RefreshMovementSpeedModifiers(uid);
if (component.CurrentState == ShadekinState.Extreme)
ApplyLightDamage(uid, 5);
}
}
}

View File

@ -23,6 +23,8 @@ using Robust.Shared.Timing;
using Content.Shared._DV.Access.Systems;
using Content.Shared._DV.Access.Components;
using Content.Shared.Mind; // DeltaV - Subdermal ID Cards
using Content.Shared.Ninja.Components; // DeltaV
using Content.Shared.Revenant.Components; // DeltaV
namespace Content.Shared.Access.Systems;
@ -31,7 +33,6 @@ public sealed class AccessReaderSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
@ -151,7 +152,7 @@ public sealed class AccessReaderSystem : EntitySystem
private void OnEmagged(EntityUid uid, AccessReaderComponent reader, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction)) // DeltaV - emag for lockers etc instead of doorjack
if (HasComp<SpaceNinjaComponent>(args.UserUid) || HasComp<RevenantComponent>(args.UserUid)) // DeltaV - Don't break access if its a ninja doing it
return;
if (!reader.BreakOnAccessBreaker)

View File

@ -1,9 +1,7 @@
using System.Linq;
using System.Numerics;
using Content.Shared._DV.Humanoid;
using Content.Shared._DV.Humanoid; // DeltaV
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid;
using Content.Shared.Sprite;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@ -18,7 +16,6 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly MarkingManager _marking = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedScaleVisualsSystem _scaleVisualsSystem = default!;
public override void Initialize()
{
@ -28,7 +25,6 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
SubscribeLocalEvent<VisualOrganMarkingsComponent, BodyRelayedEvent<OrganCopyAppearanceEvent>>(OnMarkingsOrganCopyAppearance);
SubscribeLocalEvent<VisualOrganComponent, BodyRelayedEvent<ApplyOrganProfileDataEvent>>(OnVisualOrganApplyProfile);
SubscribeLocalEvent<VisualOrganMarkingsComponent, BodyRelayedEvent<ApplyOrganMarkingsEvent>>(OnMarkingsOrganApplyMarkings);
SubscribeLocalEvent<HumanoidProfileComponent, ApplyOrganProfileDataEvent>(OnApplyOrganProfileData); // Delta V - Taking the solution from CD
InitializeModifiers();
InitializeInitial();
@ -100,36 +96,9 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
if (!other.Layer.Equals(ent.Comp.Layer))
return;
// Delta V - Begin Fix Height for Cloning
var height = other.Profile.Height;
if (TryComp<HumanoidProfileComponent>(args.Body.Owner, out var component))
ScaleBody((args.Body.Owner, component), height, height);
// Delta V - End
SetOrganAppearance(ent, other.Data);
}
// Delta V - BEGIN CD Solution
private void OnApplyOrganProfileData(Entity<HumanoidProfileComponent> entity, ref ApplyOrganProfileDataEvent args)
{
var speciesPrototype = _prototype.Index(entity.Comp.Species);
if (args.Base == null)
return;
var height = Math.Clamp(MathF.Round(args.Base.Value.Height, 2), speciesPrototype.MinHeight, speciesPrototype.MaxHeight);
ScaleBody(entity, speciesPrototype.ScaleHeight ? height : 1f, height);
}
private void ScaleBody(Entity<HumanoidProfileComponent> entity, float heightX, float heightY)
{
_scaleVisualsSystem.SetSpriteScale(
entity.Owner,
new Vector2(heightX, heightY)
);
}
// Delta V - END
private void OnMarkingsOrganCopyAppearance(Entity<VisualOrganMarkingsComponent> ent, ref BodyRelayedEvent<OrganCopyAppearanceEvent> args)
{

View File

@ -392,7 +392,6 @@ public abstract partial class SharedBuckleSystem
if (TryComp<PhysicsComponent>(buckle, out var physics))
_physics.ResetDynamics(buckle, physics);
// TOOD: DV - This fails when you try to buckle the entity you're carrying to something. Figure out why later.
DebugTools.AssertEqual(xform.ParentUid, strap.Owner);
}

View File

@ -28,4 +28,18 @@ public readonly partial record struct CargoBountyData
Bounty = bounty.ID;
Id = $"{bounty.IdPrefix}{uniqueIdentifier:D3}";
}
}
// Begin DeltaV bounty claiming
[DataField]
public string ClaimedBy { get; init; } = string.Empty;
[DataField]
public CargoBountyStatus Status { get; init; } = CargoBountyStatus.Undelivered;
}
public enum CargoBountyStatus
{
Undelivered,
Waiting,
OnShuttle,
} // End DeltaV bounty claiming

View File

@ -94,3 +94,4 @@ public sealed class BountySkipMessage : BoundUserInterfaceMessage
BountyId = bountyId;
}
}

View File

@ -26,6 +26,7 @@ using System.Linq;
using Content.Shared.Movement.Systems;
using Content.Shared.Random.Helpers;
using Content.Shared.Clothing.Components;
using Content.Shared._Starlight.Flash.Components; // Delta V - For Flash Duration
namespace Content.Shared.Flash;
@ -173,7 +174,7 @@ public abstract class SharedFlashSystem : EntitySystem
// Goobstation end
// don't paralyze, slowdown or convert to rev if the target is immune to flashes
if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, flashDuration, true) && !ignoreProtection) //DeltaV: allow flashing to ignore flash protection
if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, flashDuration * multiplier, true) && !ignoreProtection) //DeltaV: allow flashing to ignore flash protection. Added Flashduration Multiplier
return;
if (stunDuration != null)

View File

@ -2,8 +2,7 @@ using Content.Shared.Armor; // DeltaV - Addition of HandHeldArmor
using Content.Shared.Atmos;
using Content.Shared.Camera;
using Content.Shared.Cuffs;
using Content.Shared.Damage; // DeltaV End - Addition of HandHeldArmor
using Content.Shared.Damage.Systems; // DeltaV End - Addition of HandHeldArmor
using Content.Shared.Damage.Systems; // DeltaV - Addition of HandHeldArmor
using Content.Shared.Hands.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Projectiles;

View File

@ -1,7 +1,9 @@
using System.Numerics; // DeltaV
using Content.Shared.Examine;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Preferences;
using Content.Shared.Sprite; // DeltaV
using Robust.Shared.GameObjects.Components.Localization;
using Robust.Shared.Prototypes;
@ -11,6 +13,7 @@ public sealed class HumanoidProfileSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly GrammarSystem _grammar = default!;
[Dependency] private readonly SharedScaleVisualsSystem _scale = default!; // DeltaV
public override void Initialize()
{
@ -28,6 +31,7 @@ public sealed class HumanoidProfileSystem : EntitySystem
ent.Comp.Age = profile.Age;
ent.Comp.Species = profile.Species;
ent.Comp.Sex = profile.Sex;
ent.Comp.Height = profile.Height; // DeltaV
Dirty(ent);
var sexChanged = new SexChangedEvent(ent.Comp.Sex, profile.Sex);
@ -37,6 +41,19 @@ public sealed class HumanoidProfileSystem : EntitySystem
{
_grammar.SetGender((ent, grammar), profile.Gender);
}
// START DeltaV - Apply profile/species size
Vector2 scale = new(profile.Height, profile.Height);
// If visuals already exist, then re-apply
if (TryComp<ScaleVisualsComponent>(ent, out var scaledVisuals))
scale *= scaledVisuals.Scale;
var speciesProto = _prototype.Index(profile.Species);
scale *= speciesProto.BaseScale;
_scale.SetSpriteScale(ent, scale);
// END DeltaV
}
private void OnExamined(Entity<HumanoidProfileComponent> ent, ref ExaminedEvent args)

View File

@ -1,3 +1,4 @@
using System.Numerics; // DeltaV
using Content.Shared.Body;
using Content.Shared.Dataset;
using Content.Shared.Humanoid.Markings;
@ -111,7 +112,7 @@ public sealed partial class SpeciesPrototype : IPrototype
/// The base height scale for this species
/// </summary>
[DataField("baseScale")]
public System.Numerics.Vector2 BaseScale = new(1f, 1f);
public Vector2 BaseScale = new(1f, 1f);
// End DV - CD Character Records shouldn't nuke species heights
// Begin CD - Character Records
@ -119,13 +120,13 @@ public sealed partial class SpeciesPrototype : IPrototype
/// The minimum height for this species
/// </summary>
[DataField("minHeight")]
public float MinHeight = 0.9f; // DeltaV - less trolling with the heights
public float MinHeight = 0.8f; // DeltaV
/// <summary>
/// The maximum height for this species
/// </summary>
[DataField("maxHeight")]
public float MaxHeight = 1.1f; // DeltaV - less trolling with the heights
public float MaxHeight = 1.2f; // DeltaV
/// <summary>
/// The default height for this species

View File

@ -68,6 +68,7 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction TakeScreenshotNoUI = "TakeScreenshotNoUI";
public static readonly BoundKeyFunction ToggleFullscreen = "ToggleFullscreen";
public static readonly BoundKeyFunction Point = "Point";
public static readonly BoundKeyFunction OfferItem = "OfferItem"; // Floofstation
public static readonly BoundKeyFunction ZoomOut = "ZoomOut";
public static readonly BoundKeyFunction ZoomIn = "ZoomIn";
public static readonly BoundKeyFunction ResetZoom = "ResetZoom";

View File

@ -1,4 +1,4 @@
using Content.Shared._DV.Overlays;
using Content.Shared._DV.Overlays; // DeltaV
using Content.Shared._DV.Psionics.Events; // DeltaV
using Content.Shared.Armor;
using Content.Shared.Atmos;
@ -85,13 +85,15 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, WieldAttemptEvent>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, UnwieldAttemptEvent>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(RefRelayInventoryEvent);
// DeltaV Start - Psionic Events
// DeltaV Start
// Psionic Events
SubscribeLocalEvent<InventoryComponent, DispelledEvent>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, PsionicPowerUseAttemptEvent>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, TargetedByPsionicPowerEvent>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, NoosphericFryEvent>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, WeightlessnessChangedEvent>(RefRelayInventoryEvent); // Heavy Clothing
// DeltaV End - Psionic Events
// Heavy Clothing
SubscribeLocalEvent<InventoryComponent, WeightlessnessChangedEvent>(RefRelayInventoryEvent);
// DeltaV End
// Eye/vision events
SubscribeLocalEvent<InventoryComponent, CanSeeAttemptEvent>(RelayInventoryEvent);

View File

@ -103,6 +103,22 @@ public abstract class SharedItemSystem : EntitySystem
VisualsChanged(uid);
}
/// <summary>
/// DeltaV - clear item visuals
/// </summary>
public void ClearVisuals(Entity<ItemComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp))
return;
ent.Comp.RsiPath = null;
ent.Comp.InhandVisuals = new();
ent.Comp.HeldPrefix = null;
Dirty(ent, ent.Comp);
VisualsChanged(ent.Owner);
}
#endregion
private void OnHandInteract(EntityUid uid, ItemComponent component, InteractHandEvent args)

View File

@ -1,4 +1,5 @@
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
using Content.Shared.Chemistry.Components; // DeltaV - Health Analyzer Plus
using Robust.Shared.Serialization;
namespace Content.Shared.MedicalScanner;
@ -29,11 +30,12 @@ public struct HealthAnalyzerUiState
public bool? ScanMode;
public bool? Bleeding;
public bool? Unrevivable;
public readonly Solution? BloodSolution; // DeltaV - Health Analyzer Plus
public MedicalRecord? MedicalRecord; // DeltaV - Medical Records
public HealthAnalyzerUiState() {}
public HealthAnalyzerUiState(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable, MedicalRecord? medicalRecord = null) // DeltaV - Medical Records
public HealthAnalyzerUiState(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable, Solution? bloodSolution, MedicalRecord? medicalRecord = null) // DeltaV - Health Analyzer Plus, Medical Records
{
TargetEntity = targetEntity;
Temperature = temperature;
@ -41,6 +43,7 @@ public struct HealthAnalyzerUiState
ScanMode = scanMode;
Bleeding = bleeding;
Unrevivable = unrevivable;
BloodSolution = bloodSolution; // DeltaV - Health Analyzer Plus
MedicalRecord = medicalRecord; // DeltaV - Medical Records
}
}

View File

@ -1,4 +1,7 @@
using Content.Shared._DV.Body.Components; // DeltaV
using Content.Shared._DV.Body.Systems; // DeltaV
using Content.Shared._ST.Interaction; // Stellar - interaction particles
using Content.Shared._Floof.OfferItem; // Floof
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Alert;
@ -9,6 +12,7 @@ using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid; // DeltaV
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Interaction;
@ -54,6 +58,7 @@ public sealed class PullingSystem : EntitySystem
[Dependency] private readonly HeldSpeedModifierSystem _clothingMoveSpeed = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedVirtualItemSystem _virtual = default!;
[Dependency] private readonly SmallCharacterSystem _smallCharacter = default!; // DeltaV
public override void Initialize()
{
@ -117,10 +122,11 @@ public sealed class PullingSystem : EntitySystem
if (TryComp(args.PullerUid, out PullerComponent? pullerComp) && !pullerComp.NeedsHands)
return;
if (!_virtual.TrySpawnVirtualItemInHand(args.PulledUid, uid))
if (!_virtual.TrySpawnVirtualItemInHand(args.PulledUid, uid, out var virt)) // Floofstation - store item
{
DebugTools.Assert("Unable to find available hand when starting pulling??");
}
EnsureComp<OfferableVirtualItemComponent>(virt.Value); // Floofstation - add a special component to allow offering it
}
private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
@ -293,6 +299,14 @@ public sealed class PullingSystem : EntitySystem
private void OnRefreshMovespeed(EntityUid uid, PullerComponent component, RefreshMovementSpeedModifiersEvent args)
{
// BEGIN DeltaV - Slow if smaller puller
if (TryComp<SmallCharacterComponent>(uid, out var smol))
{
var sizePenalty = _smallCharacter.ApplyPullSpeedPenalty((uid, smol), component.Pulling);
args.ModifySpeed(sizePenalty, sizePenalty);
}
// END DeltaV
if (TryComp<HeldSpeedModifierComponent>(component.Pulling, out var heldMoveSpeed) && component.Pulling.HasValue)
{
var (walkMod, sprintMod) =

View File

@ -1,8 +1,7 @@
using Content.Shared.Actions;
using Content.Shared.Bed.Sleep;
using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.IdentityManagement;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Item.PseudoItem;
@ -24,7 +23,8 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; // Floofstation
[Dependency] private readonly SharedHandsSystem _hands = default!; // Floofstation
private readonly ProtoId<TagPrototype> PreventTag = "PreventLabel";
private readonly EntProtoId SleepActionId = "ActionSleep"; // The action used for sleeping inside bags. Currently uses the default sleep action (same as beds)
@ -116,17 +116,35 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
protected virtual void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component,
GettingPickedUpAttemptEvent args)
{
if (args.User == args.Item)
return;
args.Cancel(); // Floof - this is a terrible idea. This triggers every time ANY system checks if a pseudo-item can be picked up.
// WHY DID YOU DO THAT, NYANOTRASEN???
_transform.AttachToGridOrMap(uid);
args.Cancel();
// if (args.User == args.Item)
// return;
//
// _transform.AttachToGridOrMap(uid);
// args.Cancel();
}
private void OnDropAttempt(EntityUid uid, PseudoItemComponent component, DropAttemptEvent args)
{
if (component.Active)
args.Cancel();
if (!component.Active)
return;
// Floof - we try to get the containing container and try to drop it into it
// If possible, we do it, since a bagged cat probably can put things back into the bag just like they can pick them up.
string? failReason = null;
if (_hands.GetActiveItem(uid) is { Valid: true, } droppedItem
&& _container.TryGetContainingContainer(Transform(uid).ParentUid, uid, out var pseudoItemContainer)
&& TryComp<StorageComponent>(pseudoItemContainer.Owner, out var targetStorage)
&& _storage.CanInsert(pseudoItemContainer.Owner, droppedItem, out failReason, targetStorage, ignoreStacks: true)
)
_storage.Insert(pseudoItemContainer.Owner, droppedItem, out _, uid, targetStorage, stackAutomatically: false);
if (failReason != null)
_popupSystem.PopupEntity(Loc.GetString(failReason), uid, uid);
args.Cancel();
}
private void OnInsertAttempt(EntityUid uid, PseudoItemComponent component,
@ -141,8 +159,9 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
// Prevents moving within the bag :)
private void OnInteractAttempt(EntityUid uid, PseudoItemComponent component, InteractionAttemptEvent args)
{
if (args.Uid == args.Target && component.Active)
args.Cancelled = true;
// Floof - why the fuck.
// if (args.Uid == args.Target && component.Active)
// args.Cancelled = true;
}
private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args)

View File

@ -177,7 +177,7 @@ namespace Content.Shared.Preferences
_traitPreferences = traitPreferences;
_loadouts = loadouts;
// Begin CD - Character Records
Height = height;
Height = height; // This is the user-set scale on the profile editor. Not actual height measurements.
CDCharacterRecords = cdCharacterRecords;
// End CD - Character Records

View File

@ -1,4 +1,5 @@
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems; // DeltaV
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Doors.Components;
@ -19,6 +20,7 @@ namespace Content.Shared.Remotes.EntitySystems;
public abstract class SharedDoorRemoteSystem : EntitySystem
{
[Dependency] private readonly SharedAirlockSystem _airlock = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!; // DeltaV
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
[Dependency] private readonly SharedElectrocutionSystem _electrify = default!;
@ -96,6 +98,19 @@ public abstract class SharedDoorRemoteSystem : EntitySystem
else if (entity.Comp.RequireTagWhitelist)
return;
// Begin DeltaV - Emergency access only bypasses open/close; bolting and toggling emergency access still require actual access.
if (entity.Comp.Mode != OperatingMode.OpenClose
&& accessComponent != null
&& !_accessReader.IsAllowed(accessTarget, args.Target.Value, accessComponent))
{
if (isAirlock)
_doorSystem.Deny(args.Target.Value, doorComp, user: args.User, predicted: true);
_popup.PopupClient(Loc.GetString("door-remote-denied"), args.User, args.User);
return;
}
// End DeltaV
switch (entity.Comp.Mode)
{
case OperatingMode.OpenClose:

View File

@ -1,4 +1,3 @@
using Content.Shared._CD.Silicons.Borgs; // CosmicDrift - borg subtypes
using Content.Shared.Actions;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
@ -97,10 +96,6 @@ 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)
@ -125,25 +120,24 @@ public abstract partial class SharedBorgSwitchableTypeSystem : EntitySystem // D
{
footstepModifier.FootstepSoundCollection = prototype.FootstepCollection;
}
// 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
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);
}
}
}

View File

@ -24,12 +24,4 @@ public sealed partial class ScaleVisualsComponent : Component
[DataField]
[ViewVariables]
public Vector2? OriginalScale;
// Delta V Addition
/// <summary>
/// Base Scale of the Species, which we use to set a new height relative to this.
/// </summary>
[DataField, AutoNetworkedField]
[ViewVariables]
public Vector2 SpeciesScale = Vector2.One;
}

View File

@ -41,13 +41,8 @@ public abstract class SharedScaleVisualsSystem : EntitySystem
comp.Scale = scale;
Dirty(uid, comp);
// Delta V - Begin Species Scaling
// 120% species scale => add 0.2 to scale
var newScale = scale + comp.SpeciesScale - Vector2.One;
// Delta V - End Species Scaling
var appearanceComponent = EnsureComp<AppearanceComponent>(uid);
_appearance.SetData(uid, ScaleVisuals.Scale, newScale /* Delta V - Custom Species Scale */, appearanceComponent);
_appearance.SetData(uid, ScaleVisuals.Scale, scale, appearanceComponent);
// Raise an event for content use.
var ev = new ScaleEntityEvent(uid, scale);

View File

@ -1,6 +1,5 @@
using Content.Shared.Examine;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.Stealth.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Timing;

View File

@ -188,12 +188,20 @@ public abstract class SharedStorageSystem : EntitySystem
return;
}
// Begin DeltaV - the grid check excludes the item being resized because it's already in the storage which defeats the purpose so yoink it out to remove that
storage.StoredItems.Remove(itemEnt.Owner);
UpdateOccupied((container.Owner, storage));
if (!ItemFitsInGridLocation((itemEnt.Owner, itemEnt.Comp), (container.Owner, storage), loc))
if (ItemFitsInGridLocation((itemEnt.Owner, itemEnt.Comp), (container.Owner, storage), loc))
{
storage.StoredItems.Add(itemEnt.Owner, loc);
UpdateOccupied((container.Owner, storage));
}
else
{
ContainerSystem.Remove(itemEnt.Owner, container, force: true);
}
// End DeltaV - the grid check excludes the item being resized because it's already in the storage which defeats the purpose so yoink it out to remove that
}
private void OnNestedStorageCvar(bool obj)

View File

@ -1,28 +0,0 @@
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

@ -1,77 +0,0 @@
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

@ -1,18 +0,0 @@
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

@ -1,100 +0,0 @@
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

@ -1,16 +0,0 @@
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

@ -0,0 +1,23 @@
using Content.Shared._DV.Body.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Body.Components;
/// <summary>
/// If an entity has this, if a small character has penalties (such as pull speed),
/// the small character will ignore the penalties associated with their size.
///
/// Mostly used for things like wheeled/floating objects.
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(SmallCharacterSystem))]
public sealed partial class SmallCharacterComponent : Component
{
/// <summary>
/// The speed of which to scale the small character's pull speed by if the
/// object is big enough to warrant a pull-speed slowdown.
/// </summary>
[DataField, AutoNetworkedField]
public float PullSpeedPenalty = 1f;
}

View File

@ -0,0 +1,12 @@
namespace Content.Shared._DV.Body.Components;
/// <summary>
/// If an entity has this, if a small character has penalties (such as pull speed),
/// the small character will ignore the penalties associated with their size.
///
/// Mostly used for things like wheeled/floating objects.
///
/// See <see cref="Systems.SmallCharacterSystem"/>
/// </summary>
[RegisterComponent]
public sealed partial class UnaffectedBySizePenaltyComponent : Component;

View File

@ -1,5 +1,6 @@
using Content.Shared._DV.Body.Components;
using Content.Shared._DV.Body.Events;
using Content.Shared.Buckle;
using Content.Shared.DoAfter;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
@ -13,6 +14,7 @@ public sealed class CPRSystem : EntitySystem
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedBuckleSystem _buckle = default!;
public override void Initialize()
{
@ -85,7 +87,7 @@ public sealed class CPRSystem : EntitySystem
{
Act = () => StartCPR(user, target, cprComp.TimeLength),
Text = Loc.GetString("cpr-verb-start"),
Priority = 2,
Priority = _buckle.IsBuckled(target) ? 3 : 1, // Higher priority if they are buckled. Otherwise, this conflicts with trying to carry.
Disabled = alreadyAffected,
Message = alreadyAffected ? Loc.GetString("cpr-verb-disabled-description") : Loc.GetString("cpr-verb-description"),
};

View File

@ -0,0 +1,97 @@
using Content.Shared._DV.Body.Components;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Item;
using JetBrains.Annotations;
using Robust.Shared.Physics.Components;
namespace Content.Shared._DV.Body.Systems;
/// <summary>
/// Used to relay or subscribe to events if a character's scale is 1.0 or below.
/// This is only used for the height slider scale.
/// </summary>
public sealed partial class SmallCharacterSystem : EntitySystem
{
private const float NO_PENALTY = 1.0f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawn);
}
private void OnSpawn(PlayerSpawnCompleteEvent ev)
{
if (TryComp<HumanoidProfileComponent>(ev.Mob, out var profile))
ApplySmallCharacter(ev.Mob, profile.Height);
}
[PublicAPI]
public float ApplyPullSpeedPenalty(Entity<SmallCharacterComponent?> puller, EntityUid? pulledEntity)
{
// Ignore if they aren't pulling anything...
if (!pulledEntity.HasValue)
return NO_PENALTY;
// Ignore if they aren't a small character in the first place
if (!Resolve(puller, ref puller.Comp, false))
return NO_PENALTY;
// If the pulled entity has the component that ignores the penalty
if (HasComp<UnaffectedBySizePenaltyComponent>(pulledEntity))
return NO_PENALTY;
// Ignore if it's an item that can be held or stored. It would be weird to
// slow by X% from pulling a piece of paper or a gun when you can just hold it
// and not suffer from a penalty.
if (HasComp<ItemComponent>(pulledEntity))
return NO_PENALTY;
// Ignore if the object is floating in the air.
if (TryComp<PhysicsComponent>(pulledEntity, out var pulledPhysics)
&& pulledPhysics.BodyStatus == BodyStatus.InAir)
return NO_PENALTY;
return puller.Comp.PullSpeedPenalty;
}
#region Static Members
/// <summary>
/// Gets the move-speed penalty as a float. Should be applied multiplicatively.
/// Caps at 1 so we don't make bigger characters faster when pulling.
/// </summary>
/// <returns></returns>
[PublicAPI]
public static float GetPullSpeedPenaltyFromScale(float scale = 1.0f)
{
return Math.Min(scale * scale, 1);
}
/// <summary>
/// Calculates a well-formed display string of the pull speed penalty.
/// Used primarily in the character editor to get the well-formed percent
/// without having to duplicate formulas.
/// </summary>
/// <param name="scale"></param>
/// <returns></returns>
[PublicAPI]
public static string GetPullSpeedPenaltyDisplayFromScale(float scale = 1.0f)
{
return $"{Math.Round((1 - GetPullSpeedPenaltyFromScale(scale)) * 100)}%";
}
#endregion
#region Private Members
private void ApplySmallCharacter(EntityUid uid, float scale = 1)
{
if (scale >= 1)
return;
// The character scale is stored in the HumanoidProfileComponent if you ever
// need it.
var comp = EnsureComp<SmallCharacterComponent>(uid);
comp.PullSpeedPenalty = GetPullSpeedPenaltyFromScale(scale);
Dirty(uid, comp);
}
#endregion
}

View File

@ -0,0 +1,31 @@
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared._DV.Cargo.Components;
[Serializable, NetSerializable]
public sealed class BountyClaimedMessage : BoundUserInterfaceMessage
{
public readonly string BountyId;
public BountyClaimedMessage(string bountyId)
{
BountyId = bountyId;
}
}
[Serializable, NetSerializable]
public sealed class BountySetStatusMessage : BoundUserInterfaceMessage
{
public readonly string BountyId;
public readonly int Status;
public BountySetStatusMessage(string bountyId, int status)
{
BountyId = bountyId;
Status = status;
}
}

View File

@ -28,7 +28,9 @@ using Robust.Shared.Network;
using Robust.Shared.Physics.Components;
using System.Numerics;
using Content.Shared._DV.Polymorph;
using Content.Shared._Floof.OfferItem;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Buckle;
namespace Content.Shared._DV.Carrying;
@ -69,7 +71,7 @@ public sealed class CarryingSystem : EntitySystem
SubscribeLocalEvent<BeingCarriedComponent, GettingInteractedWithAttemptEvent>(OnInteractedWith);
SubscribeLocalEvent<BeingCarriedComponent, PullAttemptEvent>(OnPullAttempt);
SubscribeLocalEvent<BeingCarriedComponent, StartClimbEvent>(OnDrop);
SubscribeLocalEvent<BeingCarriedComponent, BuckledEvent>(OnDrop);
SubscribeLocalEvent<BeingCarriedComponent, BuckledEvent>(OnBuckle);
SubscribeLocalEvent<BeingCarriedComponent, UnbuckledEvent>(OnDrop);
SubscribeLocalEvent<BeingCarriedComponent, StrappedEvent>(OnDrop);
SubscribeLocalEvent<BeingCarriedComponent, UnstrappedEvent>(OnDrop);
@ -181,14 +183,15 @@ public sealed class CarryingSystem : EntitySystem
/// </summary>
private void OnInteractionAttempt(Entity<BeingCarriedComponent> ent, ref InteractionAttemptEvent args)
{
if (args.Target is not {} target)
return;
var targetParent = Transform(target).ParentUid;
var carrier = ent.Comp.Carrier;
if (target != carrier && targetParent != carrier && targetParent != ent.Owner)
args.Cancelled = true;
// Floofstation - no - this prevents the person from escaping and more.
// if (args.Target is not {} target)
// return;
//
// var targetParent = Transform(target).ParentUid;
//
// var carrier = ent.Comp.Carrier;
// if (target != carrier && targetParent != carrier && targetParent != ent.Owner)
// args.Cancelled = true;
}
private void OnMoveAttempt(Entity<BeingCarriedComponent> ent, ref UpdateCanMoveEvent args)
@ -203,8 +206,9 @@ public sealed class CarryingSystem : EntitySystem
private void OnInteractedWith(Entity<BeingCarriedComponent> ent, ref GettingInteractedWithAttemptEvent args)
{
if (args.Uid != ent.Comp.Carrier)
args.Cancelled = true;
// Floofstation - why?
// if (args.Uid != ent.Comp.Carrier)
// args.Cancelled = true;
}
private void OnPullAttempt(Entity<BeingCarriedComponent> ent, ref PullAttemptEvent args)
@ -217,6 +221,13 @@ public sealed class CarryingSystem : EntitySystem
DropCarried(ent.Comp.Carrier, ent);
}
private void OnBuckle(Entity<BeingCarriedComponent> ent, ref BuckledEvent args)
{
// Buckling to a bed already handles the reparenting to the entity that the carried
// entity is buckled to, and then relays the BuckledEvent, so don't reparent to the grid.
DropCarried(ent.Comp.Carrier, ent, attachToGrid: false);
}
private void OnRemoved(Entity<BeingCarriedComponent> ent, ref ComponentRemove args)
{
/*
@ -242,7 +253,8 @@ public sealed class CarryingSystem : EntitySystem
args.Handled = true;
}
private void StartCarryDoAfter(EntityUid carrier, Entity<CarriableComponent> carried)
// Floofstation - made public
public void StartCarryDoAfter(EntityUid carrier, Entity<CarriableComponent> carried)
{
TimeSpan length = GetPickupDuration(carrier, carried);
@ -299,7 +311,8 @@ public sealed class CarryingSystem : EntitySystem
for (var x = 0; x < Comp<CarriableComponent>(carried).FreeHandsRequired; x++)
{
_virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
if (_virtualItem.TrySpawnVirtualItemInHand(carried, carrier, out var virtualItem))
EnsureComp<OfferableVirtualItemComponent>(virtualItem.Value);
}
}
@ -322,9 +335,9 @@ public sealed class CarryingSystem : EntitySystem
return true;
}
public void DropCarried(EntityUid carrier, EntityUid carried)
public void DropCarried(EntityUid carrier, EntityUid carried, bool attachToGrid = true)
{
Drop(carried);
Drop(carried, attachToGrid);
CleanupCarrier(carrier, carried);
}
@ -336,12 +349,15 @@ public sealed class CarryingSystem : EntitySystem
_movementSpeed.RefreshMovementSpeedModifiers(carrier);
}
private void Drop(EntityUid carried)
private void Drop(EntityUid carried, bool attachToGrid = true)
{
RemComp<BeingCarriedComponent>(carried);
RemComp<KnockedDownComponent>(carried); // TODO SHITMED: make sure this doesnt let you make someone with no legs walk
_actionBlocker.UpdateCanMove(carried);
_transform.AttachToGridOrMap(carried);
// Some systems will handle re-parenting and then throw an event, and this changes the parent when it should not
if (attachToGrid)
_transform.AttachToGridOrMap(carried);
_standingState.Stand(carried);
}

View File

@ -0,0 +1,15 @@
using Content.Shared.Item;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Forensics;
[RegisterComponent, NetworkedComponent]
public sealed partial class DVExpandToInsertedItemSizeComponent : Component
{
[DataField(required: true)]
public string ItemSlot;
[DataField(required: true)]
public ProtoId<ItemSizePrototype> EmptySize;
}

View File

@ -0,0 +1,50 @@
using Content.Shared.Item;
using Robust.Shared.Containers;
namespace Content.Shared._DV.Forensics;
public sealed class DVExpandToInsertedItemSizeSystem : EntitySystem
{
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVExpandToInsertedItemSizeComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
SubscribeLocalEvent<DVExpandToInsertedItemSizeComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
}
private void Refresh(Entity<DVExpandToInsertedItemSizeComponent> ent)
{
var slot = _container.EnsureContainer<ContainerSlot>(ent, ent.Comp.ItemSlot);
if (slot.ContainedEntity is { } contained)
{
var item = Comp<ItemComponent>(contained);
_item.SetSize(ent, item.Size);
_item.SetShape(ent, item.Shape);
}
else
{
_item.SetSize(ent, ent.Comp.EmptySize);
_item.SetShape(ent, null);
}
}
private void OnItemInserted(Entity<DVExpandToInsertedItemSizeComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (ent.Comp.ItemSlot != args.Container.ID)
return;
Refresh(ent);
}
private void OnItemRemoved(Entity<DVExpandToInsertedItemSizeComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (ent.Comp.ItemSlot != args.Container.ID)
return;
Refresh(ent);
}
}

View File

@ -0,0 +1,17 @@
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Shared._DV.Forensics;
[RegisterComponent, NetworkedComponent]
public sealed partial class DVItemSlotVisualsComponent : Component
{
[DataField(required: true)]
public string ItemSlot;
[DataField(required: true)]
public SpriteSpecifier FilledSprite;
[DataField(required: true)]
public SpriteSpecifier UnfilledSprite;
}

View File

@ -0,0 +1,10 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Forensics;
[RegisterComponent, NetworkedComponent]
public sealed partial class DVSeenInsertedItemComponent : Component
{
[DataField(required: true)]
public string ItemSlot;
}

View File

@ -0,0 +1,80 @@
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Item;
using Content.Shared.NameModifier.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Forensics;
public sealed class DVSeenInsertedItemSystem : EntitySystem
{
[Dependency] private readonly NameModifierSystem _nameModifier = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DVSeenInsertedItemComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DVSeenInsertedItemComponent, RefreshNameModifiersEvent>(OnRefreshModifiers);
SubscribeLocalEvent<DVSeenInsertedItemComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
SubscribeLocalEvent<DVSeenInsertedItemComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
}
private void OnExamined(Entity<DVSeenInsertedItemComponent> ent, ref ExaminedEvent args)
{
var slot = _container.EnsureContainer<ContainerSlot>(ent, ent.Comp.ItemSlot);
if (slot.ContainedEntity is { } contained)
{
args.PushMarkup(Loc.GetString("dv-seen-inserted-item-examined.full", ("container", Identity.Entity(ent, EntityManager, args.Examiner)), ("contained", contained)));
}
else
{
args.PushMarkup(Loc.GetString("dv-seen-inserted-item-examined.empty", ("container", Identity.Entity(ent, EntityManager, args.Examiner))));
}
}
private void OnRefreshModifiers(Entity<DVSeenInsertedItemComponent> ent, ref RefreshNameModifiersEvent args)
{
var slot = _container.EnsureContainer<ContainerSlot>(ent, ent.Comp.ItemSlot);
if (slot.ContainedEntity is not { } contained)
return;
args.AddModifier("dv-seen-inserted-item-name-modifier", extraArgs: ("contained", contained));
}
private void Refresh(Entity<DVSeenInsertedItemComponent> ent)
{
_nameModifier.RefreshNameModifiers(ent.Owner);
var slot = _container.EnsureContainer<ContainerSlot>(ent, ent.Comp.ItemSlot);
if (slot.ContainedEntity is { } contained)
{
var item = Comp<ItemComponent>(contained);
_item.CopyVisuals(ent.Owner, item);
}
else
{
_item.ClearVisuals(ent.Owner);
}
}
private void OnItemInserted(Entity<DVSeenInsertedItemComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != ent.Comp.ItemSlot || _timing.ApplyingState)
return;
Refresh(ent);
}
private void OnItemRemoved(Entity<DVSeenInsertedItemComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (args.Container.ID != ent.Comp.ItemSlot || _timing.ApplyingState)
return;
Refresh(ent);
}
}

View File

@ -70,13 +70,23 @@ public abstract class SharedLightReactiveSystem : EntitySystem
/// Avoid calling this too often, as it can be expensive.
/// </summary>
public float GetLightLevelForPoint(EntityUid uid, TransformComponent? xform = null)
{
return GetLightLevelAtPosition(uid, _transform.GetWorldPosition(uid), xform);
}
/// <summary>
/// Gets the light level at an arbitrary world position, using <paramref name="uid"/> for the
/// light lookup and map resolution. Lets callers sample somewhere other than the entity's
/// centre — e.g. a point just outside a wall, so the wall's own body can occlude the ray.
/// Avoid calling this too often, as it can be expensive.
/// </summary>
public float GetLightLevelAtPosition(EntityUid uid, Vector2 pos, TransformComponent? xform = null)
{
float val = 0.0f;
// Get the current map entity so we can get a MapLightComponent from it if it has one
var map = _transform.GetMap((uid, xform));
if (TryComp(map, out MapLightComponent? mapLight))
val += (mapLight.AmbientLightColor.R + mapLight.AmbientLightColor.G + mapLight.AmbientLightColor.B) / 3f;
var pos = _transform.GetWorldPosition(uid);
foreach (var (lightUid, lightComp) in GetLights(uid))
{

View File

@ -0,0 +1,30 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Overlays.Components;
/// <summary>
/// Gives the owner darkvision: lighting still renders, but total darkness is raised to
/// <see cref="LightFloor"/> brightness instead of pitch black. Unlike night vision this keeps
/// the whole lighting gradient visible, so creatures like the Skia can judge what is and isn't dark enough for them.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class DarkVisionComponent : Component
{
/// <summary>
/// Brightness that full darkness renders at, 0-1. Rendered light is clamped to a minimum of this value.
/// </summary>
[DataField, AutoNetworkedField]
public float LightFloor = 0.2f;
/// <summary>
/// Multiplier applied to actual light on top of the floor. Values above 1 overbrighten lit areas so they are unmistakable next to the grey darkness floor.
/// </summary>
[DataField, AutoNetworkedField]
public float LightGain = 8f;
/// <summary>
/// Exponent applied to lights, to make brighter areas look notably brighter
/// </summary>
[DataField, AutoNetworkedField]
public float LightExp = 2f;
}

View File

@ -0,0 +1,36 @@
using Robust.Shared.GameStates;
using Robust.Shared.Timing;
namespace Content.Shared._DV.ShadowWalk;
/// <summary>
/// Lets this entity walk straight through solid static objects (walls, doors, windows...)
/// while the entity itself is bathed in darkness (the same light level it heals in.)
/// Mobs and projectiles always stay solid.
/// <para>
/// On collision, checks our light level. Objects we're stuck in are tagged in <see cref="PassableEntities"/>
/// </para>
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ShadowWalkerComponent : Component
{
/// <summary>
/// Light level below which an object counts as bathed in darkness.
/// If the entity has a <c>LightLevelHealthComponent</c> its DarkThreshold is used
/// instead, so objects are passable exactly where the entity would heal.
/// </summary>
[DataField]
public float DarkThreshold = 0.3f;
/// <summary>
/// Objects we're currently in. Objects in this list are never solid until we fully leave.
/// </summary>
public HashSet<EntityUid> PassableEntities = new();
/// <summary>
/// Light level for this tick, to avoid re-calculating for more than one collision a tick.
/// </summary>
public GameTick LastLightCheckTick = GameTick.Zero;
public float LastLightLevel;
}

View File

@ -0,0 +1,139 @@
using Content.Shared._DV.Body;
using Content.Shared._DV.Light;
using Content.Shared.Projectiles;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;
namespace Content.Shared._DV.ShadowWalk;
/// <summary>
/// Can walk through darkness freely.
/// </summary>
public sealed partial class SharedShadowWalkSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedLightReactiveSystem _lightReactive = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
/// <summary>
/// How far outside a tagged object's AABB the walker's centre must be before the object is untagged (and so becomes solid again on the next collision).
/// At least the walker's collision radius, so an object is never made solid while it still overlaps the walker.
/// </summary>
private const float UnstickMargin = 0.45f;
/// <summary>
/// Gamefeel. Non-walls get a bigger unstick margin so they stay unstick even if you clip into a wall. Prevents getting stuck in walls.
/// </summary>
private const float MovableUnstickMargin = 1f;
private EntityQuery<LightLevelHealthComponent> _lightHealthQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
private readonly List<EntityUid> _toRemove = [];
public override void Initialize()
{
base.Initialize();
_lightHealthQuery = GetEntityQuery<LightLevelHealthComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
SubscribeLocalEvent<ShadowWalkerComponent, PreventCollideEvent>(OnPreventCollide);
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<ShadowWalkerComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.PassableEntities.Count == 0)
continue;
var worldPos = _transform.GetWorldPosition(uid);
_toRemove.Clear();
foreach (var other in comp.PassableEntities)
{
// Untag anything we've deleted or fully walked clear of; the next collision with it will re-check the light level from scratch.
if (Deleted(other))
{
_toRemove.Add(other);
continue;
}
var margin = UnstickMargin;
// Non-statics get a bigger margin :)
if (_physicsQuery.TryComp(other, out var body) && body.BodyType != BodyType.Static)
margin += MovableUnstickMargin;
if (!_lookup.GetWorldAABB(other).Enlarged(margin).Contains(worldPos))
_toRemove.Add(other);
}
foreach (var other in _toRemove)
comp.PassableEntities.Remove(other);
}
}
private void OnPreventCollide(Entity<ShadowWalkerComponent> ent, ref PreventCollideEvent args)
{
if (args.Cancelled)
return;
// Only phase through hard blockers; sensor fixtures must keep triggering.
if (!args.OurFixture.Hard || !args.OtherFixture.Hard)
return;
// Already phasing through this one: keep it passable until we've left it (pruned in
// Update), so a light change mid-overlap can never trap us inside it.
if (ent.Comp.PassableEntities.Contains(args.OtherEntity))
{
args.Cancelled = true;
return;
}
if (!CanPhaseThrough(args.OtherEntity, args.OtherBody))
return;
// A fresh collision: only phase if the walker itself is currently in darkness.
if (!InDarkness(ent))
return;
args.Cancelled = true;
ent.Comp.PassableEntities.Add(args.OtherEntity);
}
private bool CanPhaseThrough(EntityUid other, PhysicsComponent otherBody)
{
if (otherBody.BodyType == BodyType.KinematicController)
return false;
// Bullets never pass or hit based on collision timing.
if (_projectileQuery.HasComp(other))
return false;
return true;
}
private bool InDarkness(Entity<ShadowWalkerComponent> ent)
{
// Darkness is whatever the walker heals in, if it heals in darkness at all.
var threshold = _lightHealthQuery.TryComp(ent, out var lightHealth)
? lightHealth.DarkThreshold
: ent.Comp.DarkThreshold;
var curTick = _timing.CurTick;
if (ent.Comp.LastLightCheckTick != curTick)
{
ent.Comp.LastLightLevel = _lightReactive.GetLightLevelForPoint(ent.Owner);
ent.Comp.LastLightCheckTick = curTick;
}
return ent.Comp.LastLightLevel < threshold;
}
}

View File

@ -0,0 +1,5 @@
using Content.Shared.Alert;
namespace Content.Shared._Floof.OfferItem;
public sealed partial class AcceptOfferAlertEvent : BaseAlertEvent;

View File

@ -0,0 +1,52 @@
using Content.Shared.Alert;
using Content.Shared.Inventory.VirtualItem;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._Floof.OfferItem;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[Access(typeof(SharedOfferItemSystem))]
public sealed partial class OfferItemComponent : Component
{
/// <summary>
/// Apparently this indicates whether the entity is currently choosing an entity to offer (right after pressing F).
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
public bool IsInOfferMode;
/// <summary>
/// If this is true, then someone is currently offering an item to this entity, and <see cref="ReceivingFrom"/>
/// stores the ID of that entity.
/// </summary>
[DataField, AutoNetworkedField]
public bool IsInReceiveMode;
[DataField, AutoNetworkedField]
public string? Hand;
[DataField, AutoNetworkedField]
public EntityUid? Item;
/// <summary>
/// Floofstation note. So, this is EE shitcode, so prepare for an emotional rollercoaster.
/// This field can mean TWO things. It's either the target entity this entity is offering an item to,
/// or an entity that is offering an item to this entity.
/// Whether it's one or the other is distinguished by <see cref="IsInReceiveMode"/>.<br/><br/>
///
/// In rare cases it can be both. According to my research, if entity A offers an item to entity B, and entity B offers to entity A,
/// then both entities will end up in receive mode, and they will have each other as targets. There's a check preventing offer loops
/// of length more than 2.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? ReceivingFrom;
[DataField]
public float MaxOfferDistance = 2f;
[DataField]
public ProtoId<AlertPrototype> OfferAlert = "Offer";
public EntityUid GetRealEntity(EntityManager entityManager) =>
entityManager.GetComponentOrNull<VirtualItemComponent>(Item)?.BlockingEntity ?? Item ?? EntityUid.Invalid;
}

View File

@ -0,0 +1,11 @@
using Robust.Shared.GameStates;
namespace Content.Shared._Floof.OfferItem;
/// <summary>
/// A marker component that, when applied to a virtual item, allows it to be offered using item offering.
/// Implementors have to listen on ItemTransferredEvent.
/// </summary>
[RegisterComponent]
[NetworkedComponent]
public sealed partial class OfferableVirtualItemComponent : Component;

View File

@ -0,0 +1,93 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Alert;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Popups;
using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
namespace Content.Shared._Floof.OfferItem;
public abstract partial class SharedOfferItemSystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
private void InitializeInteractions()
{
base.Initialize();
CommandBinds.Builder
.Bind(ContentKeyFunctions.OfferItem, InputCmdHandler.FromDelegate(SetInOfferMode, handle: false, outsidePrediction: false))
.Register<SharedOfferItemSystem>();
}
public override void Shutdown()
{
base.Shutdown();
CommandBinds.Unregister<SharedOfferItemSystem>();
}
/// <summary>
/// This sets IsInOfferMode to true, allowing the player to select whom to offer an item to with interaction.
/// </summary>
private void SetInOfferMode(ICommonSession? offerer)
{
if (offerer is not { } playerSession)
return;
if (playerSession.AttachedEntity is not { Valid: true } uid)
return;
if (!Exists(uid))
return;
if (!_actionBlocker.CanInteract(uid, null))
return;
if (!TryComp<OfferItemComponent>(uid, out var offerItem))
return;
if (!TryComp<HandsComponent>(uid, out var hands))
return;
if (_hands.GetActiveHand((uid, hands)) is not { } activeHandName)
return;
if (!_hands.TryGetHeldItem((uid, hands), activeHandName, out var heldItem))
return;
offerItem.Item = heldItem;
if (!offerItem.IsInOfferMode)
{
if (offerItem.Item == null)
{
_popup.PopupEntity(Loc.GetString("offer-item-empty-hand"), uid, uid);
return;
}
if (offerItem.Hand == null || offerItem.ReceivingFrom == null)
{
offerItem.IsInOfferMode = true;
offerItem.Hand = activeHandName;
Dirty(uid, offerItem);
return;
}
}
// If we're already offering an item to someone, cancel that offer
if (offerItem.ReceivingFrom != null)
{
UnReceive(offerItem.ReceivingFrom.Value, offererComp: offerItem);
offerItem.IsInOfferMode = false;
Dirty(uid, offerItem);
return;
}
UnOffer(uid, offerItem);
}
}

View File

@ -0,0 +1,414 @@
using Content.Shared._DV.Carrying;
using Content.Shared.Alert;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Timing;
// Dear contributor.
// This system is fucking unmaintainable.
// If you ever happen to touch this again, please do your best to document your changes and try to resolve mysteries surrounding this code.
// I did what I could to document the parts I managed to understand, but there is still more truth to be unveiled.
//
// HOURS_WASTED_HERE_FLOOFSTATION = 10
// HOURS_WASTED_HERE_DELTAV = 1
namespace Content.Shared._Floof.OfferItem;
public abstract partial class SharedOfferItemSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly CarryingSystem _carrying = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OfferItemComponent, AcceptOfferAlertEvent>(OnAcceptOffer);
SubscribeLocalEvent<OfferItemComponent, InteractUsingEvent>(OnInteractWithReceiver, before: [typeof(IngestionSystem)]);
SubscribeLocalEvent<OfferableVirtualItemComponent, BeforeRangedInteractEvent>(OnRangedInteractWithReceiver);
SubscribeLocalEvent<OfferItemComponent, MoveEvent>(OnMove);
SubscribeLocalEvent<BeingCarriedComponent, ItemTransferredEvent>(OnCarryTransfer);
SubscribeLocalEvent<PullableComponent, ItemTransferredEvent>(OnPulledTransfer);
InitializeInteractions();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<OfferItemComponent, HandsComponent>();
while (query.MoveNext(out var uid, out var offerItem, out var hands))
{
// If the mob no longer holds an item in the original offering hand, clear offering mode
if (offerItem.Hand != null && !_hands.TryGetHeldItem((uid, hands), offerItem.Hand, out _))
{
if (offerItem.ReceivingFrom != null)
{
UnReceive(offerItem.ReceivingFrom.Value, offererComp: offerItem);
offerItem.IsInOfferMode = false;
Dirty(uid, offerItem);
}
else
UnOffer(uid, offerItem);
}
if (!offerItem.IsInReceiveMode)
{
_alertsSystem.ClearAlert(uid, offerItem.OfferAlert);
continue;
}
_alertsSystem.ShowAlert(uid, offerItem.OfferAlert);
}
}
#region Events
private void OnAcceptOffer(Entity<OfferItemComponent> ent, ref AcceptOfferAlertEvent args)
{
Receive((ent, ent.Comp));
}
private void OnInteractWithReceiver(Entity<OfferItemComponent> receiver, ref InteractUsingEvent args)
{
if (!_timing.IsFirstTimePredicted || _timing.ApplyingState || args.Handled)
return;
if (!TryComp<OfferItemComponent>(args.User, out var offererComponent))
return;
args.Handled = CreateOffer(receiver, (args.User, offererComponent));
}
private void OnRangedInteractWithReceiver(Entity<OfferableVirtualItemComponent> virtItem, ref BeforeRangedInteractEvent args)
{
// If the entity being offered is a virtual item, InteractUsing will not be raised
// because virtual items exclude themselves from being marked as used
// If this is the case, InteractHand will be raised instead, which we can use anyway because OfferItem.Item stores the offered item
//
// We also can't check Handled here because VirtualItemSystem handles it, ffs
// This won't lead you to accidentally offering someone your gun
//
// This is shitcode, this time my shitcode. My changes to the offering system allow you to transfer carrying and pulling,
// but in order to handle these, we need to be able to intercept interactions with virtual items.
//
// Ideally this code should be rewritten to:
// a) Have each different virtual item have a distinct component (e.g. CarryingVirtualItem) which would allow to distinguish them from the rest
// b) Not rely on the InteractionSystem.
// However, I'm not in the mood to do either. And I'm too deep into the rabbit hole of getting this shit to work.
if (!_timing.IsFirstTimePredicted || _timing.ApplyingState)
return;
var receiver = args.Target;
if (!TryComp<OfferItemComponent>(receiver, out var receiverComponent))
return;
var offerer = args.User;
if (!TryComp<OfferItemComponent>(offerer, out var offererComponent) || offererComponent.Item == null)
return;
// Since this is ranged, we must also check distance, because the interaction system wont check it for us in this case
if (!Transform(offerer).Coordinates.TryDistance(EntityManager, _transform, Transform(receiver.Value).Coordinates, out var dst)
|| dst > offererComponent.MaxOfferDistance)
return;
args.Handled = CreateOffer((receiver.Value, receiverComponent), (offerer, offererComponent));
}
private void OnMove(EntityUid uid, OfferItemComponent component, MoveEvent args)
{
if (_net.IsClient) // Client often mispredicts movement, we cant trust it here
return;
if (component.ReceivingFrom == null)
return;
if (_transform.InRange(args.NewPosition, Transform(component.ReceivingFrom.Value).Coordinates, component.MaxOfferDistance))
return;
UnOffer(uid, component);
}
private void OnCarryTransfer(Entity<BeingCarriedComponent> ent, ref ItemTransferredEvent args)
{
if (args.Handled
|| args.PassedItem == args.RealItem // Means the entity is transferred NOT via carrying
|| args.RealItem is not { Valid: true } carried
|| ent.Comp.Carrier is not { Valid: true } oldCarrier)
return;
_carrying.DropCarried(oldCarrier, ent);
args.Handled = _carrying.TryCarry(args.Target, carried);
}
private void OnPulledTransfer(Entity<PullableComponent> ent, ref ItemTransferredEvent args)
{
if (args.Handled
|| args.PassedItem == args.RealItem // Means the entity is transferred NOT via pulling
|| args.RealItem is not { Valid: true } pulled)
return;
_pulling.TryStopPull(pulled, ent);
args.Handled = _pulling.TryStartPull(args.Target, ent, null, ent.Comp);
}
#endregion
#region Offering / Recieving
/// <summary>
/// Attempts to create an offer. Expects offerer.Item to already be set to the offered item, offererComponent.InReceiveMode == true.
/// Will fail if offerer == receiver or if receiver already has a set TargetOrOfferer, and that person is not the current offerer
/// </summary>
private bool CreateOffer(Entity<OfferItemComponent> receiver, Entity<OfferItemComponent> offerer)
{
var offererComponent = offerer.Comp;
var receiverComponent = receiver.Comp;
if (offerer == receiver || receiverComponent.IsInReceiveMode || !offererComponent.IsInOfferMode)
return false;
if (offererComponent.IsInReceiveMode && offererComponent.ReceivingFrom != receiver)
return false;
receiverComponent.IsInReceiveMode = true;
receiverComponent.ReceivingFrom = offerer;
Dirty(receiver, receiverComponent);
offererComponent.ReceivingFrom = receiver; // TODO this is ee shitcode, may not be necessary?
offererComponent.IsInOfferMode = false;
Dirty(offerer, offererComponent);
if (offererComponent.Item == null)
return false;
// Sender popup (client-side only)
_popup.PopupClient(
Loc.GetString("offer-item-try-give",
("item", Identity.Entity(offererComponent.GetRealEntity(EntityManager), EntityManager)),
("target", Identity.Entity(receiver, EntityManager))),
offerer,
offerer);
// Receiver popup (server side only, not predicted because recipient != local player)
_popup.PopupEntity(
Loc.GetString("offer-item-try-give-target",
("user", Identity.Entity(receiverComponent.ReceivingFrom.Value, EntityManager)),
("item", Identity.Entity(offererComponent.GetRealEntity(EntityManager), EntityManager))),
offerer,
receiver,
Popups.PopupType.Medium);
return true;
}
/// <summary>
/// Resets the <see cref="OfferItemComponent"/> of the user and the target
/// </summary>
protected void UnOffer(EntityUid thisEntity, OfferItemComponent offererComp)
{
if (!TryComp<HandsComponent>(thisEntity, out var hands) || _hands.GetActiveHand((thisEntity, hands)) is null)
return;
if (offererComp.ReceivingFrom is { } otherEntity && TryComp<OfferItemComponent>(otherEntity, out var otherOfferer))
{
// So this tries to figure out which of these entities do what...
// if A.OfferItemComponent.Item != null, then A is currently offering an item to A.OfferItemComponent.TargetOrOfferer
// If it is null, then it is ONLY being offered an item TO.
if (offererComp.Item != null && _net.IsServer)
{
_popup.PopupEntity(
Loc.GetString("offer-item-no-give",
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager)), // Floof - resolve virtual items
("target", Identity.Entity(otherEntity, EntityManager))),
thisEntity,
thisEntity);
_popup.PopupEntity(
Loc.GetString("offer-item-no-give-target",
("user", Identity.Entity(thisEntity, EntityManager)),
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager))),
thisEntity,
otherEntity);
}
else if (otherOfferer.Item != null && _net.IsServer)
{
_popup.PopupEntity(
Loc.GetString("offer-item-no-give",
("item", Identity.Entity(otherOfferer.GetRealEntity(EntityManager), EntityManager)), // Floof - resolve virtual items
("target", Identity.Entity(thisEntity, EntityManager))),
otherEntity,
otherEntity);
_popup.PopupEntity(
Loc.GetString("offer-item-no-give-target",
("user", Identity.Entity(otherEntity, EntityManager)),
("item", Identity.Entity(otherOfferer.GetRealEntity(EntityManager), EntityManager))),
otherEntity,
thisEntity);
}
otherOfferer.IsInOfferMode = false;
otherOfferer.IsInReceiveMode = false;
otherOfferer.Hand = null;
otherOfferer.ReceivingFrom = null;
otherOfferer.Item = null;
Dirty(otherEntity, otherOfferer);
}
offererComp.IsInOfferMode = false;
offererComp.IsInReceiveMode = false;
offererComp.Hand = null;
offererComp.ReceivingFrom = null;
offererComp.Item = null;
Dirty(thisEntity, offererComp);
}
/// <summary>
/// Cancels the transfer of the item
/// </summary>
protected void UnReceive(EntityUid receiver, OfferItemComponent? receiverComp = null, OfferItemComponent? offererComp = null)
{
if (!Resolve(receiver, ref receiverComp)
|| receiverComp.ReceivingFrom is not {} offerer
|| !Resolve(offerer, ref offererComp))
return;
// Idk why this check is here
if (!TryComp<HandsComponent>(receiver, out var hands) || _hands.GetActiveHand((receiver, hands)) == null || receiverComp.ReceivingFrom == null)
return;
// If offererComp.Item != null, then they are actively offering to TargetOrOfferer
// Normally this method is called right after a transfer is done, but this part can be called from SetInOfferMode when the player presses F again to cancel an ongoing offer
if (offererComp.Item != null)
{
_popup.PopupClient(
Loc.GetString("offer-item-no-give",
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager)), // Floof - resolve virtual items
("target", Identity.Entity(receiver, EntityManager))),
offerer,
offerer);
_popup.PopupEntity(
Loc.GetString("offer-item-no-give-target",
("user", Identity.Entity(receiverComp.ReceivingFrom.Value, EntityManager)), // Floof - resolve virtual items
("item", Identity.Entity(offererComp.GetRealEntity(EntityManager), EntityManager))),
offerer,
receiver);
}
if (!offererComp.IsInReceiveMode)
{
offererComp.ReceivingFrom = null;
receiverComp.ReceivingFrom = null;
}
offererComp.Item = null;
offererComp.Hand = null;
receiverComp.IsInReceiveMode = false;
Dirty(receiver, receiverComp);
}
/// <summary>
/// Accepting the offer and receive item
/// </summary>
public void Receive(Entity<OfferItemComponent?> receiver)
{
if (!_timing.IsFirstTimePredicted)
return;
if (!Resolve(receiver, ref receiver.Comp))
return;
if (!TryComp<OfferItemComponent>(receiver.Comp.ReceivingFrom, out var offererComponent) ||
offererComponent.Hand == null ||
receiver.Comp.ReceivingFrom is not {} sender ||
!TryComp<HandsComponent>(receiver, out var hands))
return;
if (offererComponent.Item != null)
{
// Floof - check if there's something else handling it first
var realItem = offererComponent.GetRealEntity(EntityManager);
if (!TryHandleExtendedTransfer(sender, receiver, offererComponent.Item.Value, realItem)
&& !_hands.TryPickup(receiver, offererComponent.Item.Value, handsComp: hands))
{
_popup.PopupEntity(Loc.GetString("offer-item-full-hand"), receiver, receiver);
return;
}
_popup.PopupEntity(
Loc.GetString("offer-item-give",
("item", Identity.Entity(realItem, EntityManager)), // FLoof - resolve virtual items
("target", Identity.Entity(receiver, EntityManager))),
sender,
sender);
_popup.PopupEntity(
Loc.GetString("offer-item-give-other",
("user", Identity.Entity(receiver.Comp.ReceivingFrom.Value, EntityManager)),
("item", Identity.Entity(realItem, EntityManager)), // FLoof - resolve virtual items
("target", Identity.Entity(receiver, EntityManager))),
sender,
Filter.PvsExcept(sender, entityManager: EntityManager),
true);
}
offererComponent.Item = null;
UnReceive(receiver, receiver.Comp, offererComponent);
}
#endregion
/// <summary>
/// Returns true if <see cref="OfferItemComponent.IsInOfferMode"/> = true
/// </summary>
protected bool IsInOfferMode(Entity<OfferItemComponent?> ent)
{
return Resolve(ent, ref ent.Comp, false) && ent.Comp.IsInOfferMode;
}
private bool TryHandleExtendedTransfer(EntityUid user, EntityUid target, EntityUid offeredItem, EntityUid realItem)
{
var ev = new ItemTransferredEvent
{
User = user,
Target = target,
PassedItem = offeredItem,
RealItem = realItem,
};
RaiseLocalEvent(realItem, ref ev);
return ev.Handled;
}
}
/// <summary>
/// Raised on the entity that was transferred via item offering.
/// </summary>
[ByRefEvent]
public sealed class ItemTransferredEvent : HandledEntityEventArgs
{
public EntityUid User;
public EntityUid Target;
/// <summary>
/// The actual item being passed around. Can be a virtual item.
/// </summary>
public EntityUid PassedItem;
/// <summary>
/// If <see cref="PassedItem"/> is a virtual item, this field contains the real item that was transferred.
/// </summary>
public EntityUid? RealItem;
}

View File

@ -0,0 +1,66 @@
using Robust.Shared.GameStates;
namespace Content.Shared._Impstation.Clothing;
/// <summary>
/// Adds examine text to the entity that wears item, for making things obvious.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(WearerGetsExamineTextSystem))]
public sealed partial class WearerGetsExamineTextComponent : Component
{
/// <summary>
/// The LocId that specifies what category of object this is.
/// i.e. "pin" or "scarf"
/// Should be redefined on a per-category basis, naturally.
/// </summary>
[DataField("thing")]
public LocId Category = "obvious-thing-default";
/// <summary>
/// The LocId that specifies what member of the category this is.
/// i.e. "lesbian pride"
/// Can be used to define text colors that are copied to all things
/// which share this specifier (i.e. the other items of the same pride).
/// (And summarily, makes accessibility-based changes for these colors a cinch.)
/// Should be defined by each thing that has this component.
/// </summary>
[DataField("thingType")]
public LocId Specifier = "obvious-type-default";
/// <summary>
/// The LocId that will be added to any wearing entity's examination.
/// Typically only needs redefining on a per-category basis,
/// but items that should have totally-unique obvious text can simply specify them here.
/// </summary>
[DataField("examineText", required: true)]
public LocId ExamineOnWearer = "obvious-desc-default";
/// <summary>
/// Reference to the entity wearing this clothing.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Wearer;
/// <summary>
/// The string that is attached to this item's ExamineOnWearer.
/// Typically doesn't need to be redefined.
/// </summary>
[DataField]
public LocId PrefixExamineOnWearer = "obvious-prefix-wearing";
/// <summary>
/// If true, an entity with this item in any slot (i.e. in pockets) will gain the examine text,
/// instead of when just equipped as clothing.
/// Should be used sparingly only when truly appropriate; this is effectively a half-measure for lack of a special pin slot.
/// </summary>
[DataField]
public bool PocketEvident;
/// <summary>
/// If true, the entity's description will inform examiners what others will see on the wearer (before they equip it).
/// If the item is contraband, the item will also warn that displaying it may cause undue attention.
/// Keep this false for good-natured jokes (i.e. the pride cloaks having funny, non-pride names)
/// </summary>
[DataField]
public bool WarnExamine = true;
}

View File

@ -0,0 +1,111 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory.Events;
using Content.Shared.Contraband;
using Content.Shared._Impstation.Examine;
using System.Text;
namespace Content.Shared._Impstation.Clothing;
/// <summary>
/// Adds examine text to the entity that wears item, for making things obvious.
/// </summary>
public sealed class WearerGetsExamineTextSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WearerGetsExamineTextComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<WearerGetsExamineTextComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<WearerGetsExamineTextComponent, ExaminedEvent>(OnExamine);
}
private void OnEquipped(Entity<WearerGetsExamineTextComponent> entity, ref GotEquippedEvent args)
{
if (!TryComp(entity, out ClothingComponent? clothing))
return;
var isCorrectSlot = (clothing.Slots & args.SlotFlags) != Inventory.SlotFlags.NONE;
if (!entity.Comp.PocketEvident) //if it can't be evident in our pockets
{
// Make sure the clothing item was equipped to the right slot, and not just held in a hand.
if (!isCorrectSlot)
return;
}
entity.Comp.Wearer = args.Equipee;
Dirty(entity);
//GIVE THEM INSPECT TEXT
var obviousExamine = EnsureComp<ExtraExamineTextComponent>(args.Equipee);
obviousExamine.Lines.TryAdd(entity.Owner, //using try so that we don't cause an error if we move something from slot to slot
ConstructExamineText(entity, !isCorrectSlot, args.Equipee));
}
private string ConstructExamineText(Entity<WearerGetsExamineTextComponent> entity, bool prefixFallback, EntityUid affecting)
{
//parameters (these are the same between both constructions)
var user = Identity.Entity(affecting, EntityManager);
var nomen = Identity.Name(affecting, EntityManager);
var thing = Loc.GetString(entity.Comp.Category);
var type = Loc.GetString(entity.Comp.Specifier);
var stringSpec = entity.Comp.Specifier.ToString();
var shortType = stringSpec.Substring(stringSpec.LastIndexOf('-')); // necessary for working with colored text...
var prefix = Loc.GetString(prefixFallback ? "obvious-prefix-default" : entity.Comp.PrefixExamineOnWearer, // uses a different prefix if worn / displayed
("user", user),
("name", nomen),
("thing", thing),
("type", type));
var suffix = Loc.GetString(entity.Comp.ExamineOnWearer,
("user", user),
("name", nomen),
("thing", thing),
("type", type),
("short-type", shortType));
return prefix + " " + suffix;
}
private void OnUnequipped(Entity<WearerGetsExamineTextComponent> entity, ref GotUnequippedEvent args)
{
if (entity.Comp.Wearer is not { } wearer)
return;
if (TryComp(wearer, out ExtraExamineTextComponent? obviousExamine))
{
obviousExamine.Lines.Remove(entity.Owner);
}
entity.Comp.Wearer = null;
Dirty(entity);
}
private void OnExamine(Entity<WearerGetsExamineTextComponent> entity, ref ExaminedEvent args)
{
var currentlyWorn = entity.Comp.Wearer != null;
var outString = new StringBuilder(Loc.GetString(currentlyWorn ? "obvious-on-item-currently" : "obvious-on-item",
("used", Loc.GetString(entity.Comp.PocketEvident ? "obvious-reveal-pockets" : "obvious-reveal-default")),
("thing", entity.Comp.Category),
("me", Identity.Entity(entity, EntityManager))));
if (entity.Comp.WarnExamine)
{
if (!currentlyWorn && TryComp(entity, out ContrabandComponent? contra)) // if the item's contra and we're not wearing it yet
{
var contraLocId = "obvious-on-item-contra-" + contra.Severity; // apply additional text if the item is contraband to note that displaying it might be really bad
if (Loc.HasString(contraLocId)) // saves us the trouble of making a switch block for this
outString.Append(" " + Loc.GetString(contraLocId));
}
var affecting = currentlyWorn ? entity.Comp.Wearer.GetValueOrDefault() : args.Examiner;
var testOut = ConstructExamineText(entity, false, affecting);
outString.Append("\n" + Loc.GetString("obvious-on-item-for-others",
("will", currentlyWorn ? "can" : "will"), // i love hardcoding strings it's my favorite thing ever
("output", testOut)));
}
args.PushMarkup(outString.ToString());
}
}

View File

@ -0,0 +1,40 @@
using Robust.Shared.GameStates;
namespace Content.Shared._Impstation.Examine;
[RegisterComponent, NetworkedComponent]
public sealed partial class DetailedInspectComponent : Component
{
[DataField]
public LocId VerbText = "verbs-detailed-inspect";
[DataField]
public LocId VerbMessage = "verbs-detailed-inspect-message";
[DataField(required: true)]
public List<LocId> ExamineText;
/// <summary>
/// Whether or not the entries in ExamineText are separated by linebreaks.
/// </summary>
[DataField]
public bool LineBreak = true;
/// <summary>
/// Whether or not the entries in ExamineText are preceded by ticks.
/// </summary>
[DataField]
public bool TickEntries = false;
/// <summary>
/// Whether or not entries in the list are numbered.
/// </summary>
[DataField]
public bool NumberedEntries = false;
/// <summary>
/// Rooted directory of the icon for the verb.
/// </summary>
[DataField]
public string Icon = "/Textures/Interface/VerbIcons/dot.svg.192dpi.png";
}

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