Merge remote-tracking branch 'wizden/offmed-staging'
Some checks failed
Map file schema validator / YAML map schema validator (push) Has been cancelled
RSI Validator / Validate RSIs (push) Has been cancelled
RGA schema validator / YAML RGA schema validator (push) Has been cancelled
Test Packaging / Test Packaging (push) Has been cancelled
Build & Test Debug / build (ubuntu-latest) (push) Has been cancelled
Build & Test Debug / Build & Test Debug (push) Has been cancelled
YAML Linter / YAML Linter (push) Has been cancelled
Build & Test Map Renderer / build (ubuntu-latest) (push) Has been cancelled
Build & Test Map Renderer / Build & Test Debug (push) Has been cancelled
Benchmarks / Run Benchmarks (push) Waiting to run

This commit is contained in:
hereelabs
2025-11-23 22:52:14 -05:00
512 changed files with 17533 additions and 508 deletions

View File

@@ -1,64 +1,110 @@
<!-- offbrand completely redid this file, use offbrand's version if there are merge conflicts -->
<controls:FancyWindow <controls:FancyWindow
xmlns="https://spacestation14.io" xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MaxHeight="525" MaxHeight="725"
MinWidth="300"> MinWidth="600">
<ScrollContainer <ScrollContainer
Margin="5 5 5 5" Margin="5 5 5 5"
ReturnMeasure="True" ReturnMeasure="True"
VerticalExpand="True"> VerticalExpand="True">
<BoxContainer <BoxContainer Orientation="Horizontal">
Name="RootContainer"
VerticalExpand="True"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<BoxContainer <BoxContainer
Name="PatientDataContainer" Name="LeftContainer"
Margin="0 0 0 5" VerticalExpand="True"
Orientation="Vertical"> Orientation="Vertical"
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5"> MinWidth="300">
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" /> <Label
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/> Name="NoPatientDataText"
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top"> Text="{Loc health-analyzer-window-no-patient-data-text}" />
<RichTextLabel Name="NameLabel" SetWidth="150" />
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" /> <BoxContainer
Name="PatientDataContainer"
Margin="0 0 0 5"
Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
<RichTextLabel Name="NameLabel" SetWidth="150" />
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
</BoxContainer>
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
</BoxContainer> </BoxContainer>
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel" <PanelContainer StyleClasses="LowDivider" />
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
<GridContainer Margin="0 5 0 0" Columns="3">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<RichTextLabel Name="StatusLabel" />
<TextureButton Name="StatusButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="BrainHealthText" Text="{Loc 'health-analyzer-window-entity-brain-health-text'}" />
<RichTextLabel Name="BrainHealthLabel" />
<TextureButton Name="BrainHealthButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="SpO2Text" />
<RichTextLabel Name="SpO2Label" />
<TextureButton Name="SpO2Button" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="BloodPressureText" Text="{Loc 'health-analyzer-window-entity-blood-pressure-text'}" />
<RichTextLabel Name="BloodPressureLabel" />
<TextureButton Name="BloodPressureButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="HeartRateText" Text="{Loc 'health-analyzer-window-entity-heart-rate-text'}" />
<RichTextLabel Name="HeartRateLabel" />
<TextureButton Name="HeartRateButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="RespiratoryRateText" Text="{Loc 'health-analyzer-window-entity-respiratory-rate-text'}" />
<RichTextLabel Name="RespiratoryRateLabel" />
<TextureButton Name="RespiratoryRateButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="EtCO2Text" />
<RichTextLabel Name="EtCO2Label" />
<TextureButton Name="EtCO2Button" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="LungHealthText" Text="{Loc 'health-analyzer-window-entity-lung-health-text'}" />
<RichTextLabel Name="LungHealthLabel" />
<TextureButton Name="LungHealthButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="HeartHealthText" Text="{Loc 'health-analyzer-window-entity-heart-health-text'}" />
<RichTextLabel Name="HeartHealthLabel" />
<TextureButton Name="HeartHealthButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="BloodText" Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
<RichTextLabel Name="BloodLabel" />
<TextureButton Name="BloodButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
<RichTextLabel Name="TemperatureLabel" />
<TextureButton Name="TemperatureButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<RichTextLabel Name="DamageLabel" />
<TextureButton Name="DamageButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
</GridContainer>
</BoxContainer> </BoxContainer>
<PanelContainer StyleClasses="LowDivider" /> <PanelContainer Name="ReagentsDivider" Visible="False" StyleClasses="LowDivider" />
<GridContainer Margin="0 5 0 0" Columns="2"> <BoxContainer Name="ReagentsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalExpand="True" />
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<Label Name="StatusLabel" />
<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" />
</GridContainer>
</BoxContainer> </BoxContainer>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" /> <PanelContainer StyleClasses="LowDivider" VerticalExpand="True" Margin="0 0 0 0" SetWidth="2" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer <BoxContainer
Name="GroupsContainer" Name="RightContainer"
Margin="0 5 0 5" VerticalExpand="True"
Orientation="Vertical"> Orientation="Vertical"
</BoxContainer> MinWidth="300">
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center"/>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
<RichTextLabel
Name="NoDamagesText"
HorizontalAlignment="Center"
VerticalExpand="True"
Text="{Loc health-analyzer-window-no-patient-damages}" />
</BoxContainer>
</BoxContainer> </BoxContainer>
</ScrollContainer> </ScrollContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -2,6 +2,7 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared._Offbrand.Wounds; // Offbrand
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
@@ -30,6 +31,21 @@ namespace Content.Client.HealthAnalyzer.UI
private readonly IPrototypeManager _prototypes; private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache; private readonly IResourceCache _cache;
// Begin Offbrand
private readonly Tooltips.StatusTooltip _statusTooltip = new();
private readonly Tooltips.BrainHealthTooltip _brainHealthTooltip = new();
private readonly Tooltips.BloodPressureTooltip _bloodPressureTooltip = new();
private readonly Tooltips.HeartRateTooltip _heartRateTooltip = new();
private readonly Tooltips.HeartHealthTooltip _heartHealthTooltip = new();
private readonly Tooltips.LungHealthTooltip _lungHealthTooltip = new();
private readonly Tooltips.BloodTooltip _bloodTooltip = new();
private readonly Tooltips.TemperatureTooltip _temperatureTooltip = new();
private readonly Tooltips.DamageTooltip _damageTooltip = new();
private readonly Tooltips.SpO2Tooltip _spo2Tooltip = new();
private readonly Tooltips.EtCO2Tooltip _etco2Tooltip = new();
private readonly Tooltips.RespiratoryRateTooltip _respiratoryRateTooltip = new();
// End Offbrand
public HealthAnalyzerWindow() public HealthAnalyzerWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -39,6 +55,21 @@ namespace Content.Client.HealthAnalyzer.UI
_spriteSystem = _entityManager.System<SpriteSystem>(); _spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>(); _prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>(); _cache = dependencies.Resolve<IResourceCache>();
// Begin Offbrand
StatusButton.TooltipSupplier = _ => _statusTooltip;
BrainHealthButton.TooltipSupplier = _ => _brainHealthTooltip;
BloodPressureButton.TooltipSupplier = _ => _bloodPressureTooltip;
HeartRateButton.TooltipSupplier = _ => _heartRateTooltip;
HeartHealthButton.TooltipSupplier = _ => _heartHealthTooltip;
TemperatureButton.TooltipSupplier = _ => _temperatureTooltip;
DamageButton.TooltipSupplier = _ => _damageTooltip;
BloodButton.TooltipSupplier = _ => _bloodTooltip;
LungHealthButton.TooltipSupplier = _ => _lungHealthTooltip;
SpO2Button.TooltipSupplier = _ => _spo2Tooltip;
EtCO2Button.TooltipSupplier = _ => _etco2Tooltip;
RespiratoryRateButton.TooltipSupplier = _ => _respiratoryRateTooltip;
// End Offbrand
} }
public void Populate(HealthAnalyzerScannedUserMessage msg) public void Populate(HealthAnalyzerScannedUserMessage msg)
@@ -54,6 +85,16 @@ namespace Content.Client.HealthAnalyzer.UI
NoPatientDataText.Visible = false; NoPatientDataText.Visible = false;
// Begin Offbrand Tooltips
_brainHealthTooltip.Update(msg);
_heartRateTooltip.Update(msg);
_heartHealthTooltip.Update(msg);
_temperatureTooltip.Update(msg, (target.Value, _entityManager.GetComponentOrNull<CryostasisFactorComponent>(target)));
_spo2Tooltip.Update(msg);
_etco2Tooltip.Update(msg);
_respiratoryRateTooltip.Update(msg);
// End Offbrand Tooltips
// Scan Mode // Scan Mode
ScanModeLabel.Text = msg.ScanMode.HasValue ScanModeLabel.Text = msg.ScanMode.HasValue
@@ -104,7 +145,7 @@ namespace Content.Client.HealthAnalyzer.UI
// Alerts // Alerts
var showAlerts = msg.Unrevivable == true || msg.Bleeding == true; var showAlerts = msg.Unrevivable == true || msg.Bleeding == true || msg.WoundableData?.NonMedicalReagents == true || msg.WoundableData?.Wounds != null; // Offbrand
AlertsDivider.Visible = showAlerts; AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts; AlertsContainer.Visible = showAlerts;
@@ -128,6 +169,147 @@ namespace Content.Client.HealthAnalyzer.UI
MaxWidth = 300 MaxWidth = 300
}); });
// Begin Offbrand
var showReagents = msg.WoundableData?.Reagents?.Count is { } count && count > 0;
ReagentsDivider.Visible = showReagents;
ReagentsContainer.Visible = showReagents;
if (msg.WoundableData is { } woundable)
{
if (woundable.Wounds is not null)
{
foreach (var wound in woundable.Wounds)
{
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString(wound),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
}
}
if (woundable.NonMedicalReagents)
{
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-non-medical-reagents"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
}
if (woundable.Reagents is { } reagents)
{
ReagentsContainer.DisposeAllChildren();
foreach (var (reagent, amounts) in reagents.OrderBy(kvp => _prototypes.Index(kvp.Key).LocalizedName))
{
var (quantity, metabolites) = amounts;
var proto = _prototypes.Index(reagent);
ReagentsContainer.AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
Children =
{
new PanelContainer
{
VerticalExpand = true,
MinWidth = 4,
PanelOverride = new StyleBoxFlat
{
BackgroundColor = proto.SubstanceColor
},
Margin = new Thickness(4, 1),
},
new Label { Text = proto.LocalizedName, HorizontalExpand = true, SizeFlagsStretchRatio = 3 },
new Label { Text = $"{metabolites}u", StyleClasses = { Content.Client.Stylesheets.StyleNano.StyleClassLabelSecondaryColor }, HorizontalExpand = true, SizeFlagsStretchRatio = 1 },
new Label { Text = $"{quantity}u", HorizontalExpand = true, SizeFlagsStretchRatio = 1 },
}
});
}
}
BrainHealthText.Visible = true;
BrainHealthLabel.Visible = true;
BrainHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-brain-health-value", ("value", $"{woundable.BrainHealth * 100:F1}"));
BrainHealthButton.Visible = true;
HeartHealthText.Visible = true;
HeartHealthLabel.Visible = true;
HeartHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-health-value", ("value", $"{woundable.HeartHealth * 100:F1}"));
HeartHealthButton.Visible = true;
HeartRateText.Visible = true;
HeartRateLabel.Visible = true;
HeartRateLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-rate-value", ("value", woundable.HeartRate));
HeartRateButton.Visible = true;
var (systolic, diastolic) = woundable.BloodPressure;
BloodPressureText.Visible = true;
BloodPressureLabel.Visible = true;
BloodPressureLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-pressure-value", ("systolic", systolic), ("diastolic", diastolic));
BloodPressureButton.Visible = true;
LungHealthText.Visible = true;
LungHealthLabel.Visible = true;
LungHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-lung-health-value", ("value", $"{woundable.LungHealth * 100:F1}"));
LungHealthButton.Visible = true;
SpO2Text.Visible = true;
SpO2Text.Text = Loc.GetString("health-analyzer-window-entity-spo2-text", ("spo2", woundable.Spo2Name));
SpO2Label.Visible = true;
SpO2Label.Text = Loc.GetString("health-analyzer-window-entity-spo2-value", ("value", $"{woundable.Spo2 * 100:F1}"));
SpO2Button.Visible = true;
EtCO2Text.Visible = true;
EtCO2Text.Text = Loc.GetString("health-analyzer-window-entity-etco2-text", ("etco2", woundable.Etco2Name));
EtCO2Label.Visible = true;
EtCO2Label.Text = Loc.GetString("health-analyzer-window-entity-etco2-value", ("value", $"{woundable.Etco2}"));
EtCO2Button.Visible = true;
RespiratoryRateText.Visible = true;
RespiratoryRateLabel.Visible = true;
RespiratoryRateLabel.Text = Loc.GetString("health-analyzer-window-entity-respiratory-rate-value", ("value", $"{woundable.RespiratoryRate}"));
RespiratoryRateButton.Visible = true;
BloodLabel.Visible = false;
BloodText.Visible = false;
BloodButton.Visible = false;
}
else
{
BrainHealthLabel.Visible = false;
BloodPressureLabel.Visible = false;
HeartRateLabel.Visible = false;
HeartHealthLabel.Visible = false;
LungHealthLabel.Visible = false;
BrainHealthText.Visible = false;
BloodPressureText.Visible = false;
HeartRateText.Visible = false;
HeartHealthText.Visible = false;
LungHealthText.Visible = false;
BrainHealthButton.Visible = false;
BloodPressureButton.Visible = false;
HeartRateButton.Visible = false;
HeartHealthButton.Visible = false;
LungHealthButton.Visible = false;
SpO2Text.Visible = false;
SpO2Label.Visible = false;
SpO2Button.Visible = false;
EtCO2Text.Visible = false;
EtCO2Label.Visible = false;
EtCO2Button.Visible = false;
RespiratoryRateText.Visible = false;
RespiratoryRateLabel.Visible = false;
RespiratoryRateButton.Visible = false;
BloodLabel.Visible = true;
BloodText.Visible = true;
BloodButton.Visible = true;
}
// End Offbrand
// Damage Groups // Damage Groups
var damageSortedGroups = var damageSortedGroups =
@@ -194,6 +376,10 @@ namespace Content.Client.HealthAnalyzer.UI
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · "))); groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
} }
} }
// Begin Offbrand
NoDamagesText.Visible = GroupsContainer.ChildCount == 0;
// End Offbrand
} }
private Texture GetTexture(string texture) private Texture GetTexture(string texture)

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class BloodPressureTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-blood-pressure-tooltip";
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class BloodTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-blood-tooltip";
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.MedicalScanner;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class BrainHealthTooltip : UpdatableTooltip
{
public override void Update(HealthAnalyzerScannedUserMessage msg)
{
if (msg.WoundableData is not { } woundable)
return;
Label.Text = Loc.GetString("health-analyzer-brain-health-tooltip", ("dead", woundable.BrainHealth <= 0), ("spo2", $"{woundable.Spo2 * 100:F1}"));
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class DamageTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-damage-tooltip";
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.MedicalScanner;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class EtCO2Tooltip : UpdatableTooltip
{
public override void Update(HealthAnalyzerScannedUserMessage msg)
{
if (msg.WoundableData is not { } woundable)
return;
Label.Text = Loc.GetString("health-analyzer-etco2-tooltip", ("gas", woundable.Etco2GasName), ("etco2", woundable.Etco2Name));
}
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.MedicalScanner;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class HeartHealthTooltip : UpdatableTooltip
{
public override void Update(HealthAnalyzerScannedUserMessage msg)
{
if (msg.WoundableData is not { } woundable)
return;
Label.Text = Loc.GetString("health-analyzer-heart-health-tooltip", ("heartrate", woundable.HeartRate));
}
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.MedicalScanner;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class HeartRateTooltip : UpdatableTooltip
{
public override void Update(HealthAnalyzerScannedUserMessage msg)
{
if (msg.WoundableData is not { } woundable)
return;
Label.Text = Loc.GetString("health-analyzer-heart-rate-tooltip", ("spo2", woundable.Spo2Name));
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class LungHealthTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-lung-health-tooltip";
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.MedicalScanner;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class RespiratoryRateTooltip : UpdatableTooltip
{
public override void Update(HealthAnalyzerScannedUserMessage msg)
{
if (msg.WoundableData is not { } woundable)
return;
Label.Text = Loc.GetString("health-analyzer-respiratory-rate-tooltip", ("etco2gas", woundable.Etco2GasName), ("etco2", woundable.Etco2Name), ("spo2gas", woundable.Spo2GasName), ("spo2", woundable.Spo2Name));
}
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.MedicalScanner;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class SpO2Tooltip : UpdatableTooltip
{
public override void Update(HealthAnalyzerScannedUserMessage msg)
{
if (msg.WoundableData is not { } woundable)
return;
Label.Text = Loc.GetString("health-analyzer-spo2-tooltip", ("gas", woundable.Spo2GasName), ("spo2", woundable.Spo2Name));
}
}

View File

@@ -0,0 +1,8 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox" MaxWidth="450">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -0,0 +1,20 @@
using Content.Shared.MedicalScanner;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
[GenerateTypedNameReferences]
[Virtual]
public partial class StaticTooltip : PanelContainer
{
public StaticTooltip()
{
RobustXamlLoader.Load(this);
Label.Text = Loc.GetString(Text);
}
public virtual LocId Text => throw new NotImplementedException();
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class StatusTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-status-tooltip";
}

View File

@@ -0,0 +1,8 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -0,0 +1,31 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.FixedPoint;
using Content.Shared.MedicalScanner;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
[GenerateTypedNameReferences]
public sealed partial class TemperatureTooltip : PanelContainer
{
public TemperatureTooltip()
{
RobustXamlLoader.Load(this);
}
public void Update(HealthAnalyzerScannedUserMessage msg, Entity<CryostasisFactorComponent?> ent)
{
if (ent.Comp is null)
{
Label.Text = Loc.GetString("health-analyzer-plain-temperature-tooltip");
}
else
{
var factor = Math.Max(ent.Comp.TemperatureCoefficient * msg.Temperature + ent.Comp.TemperatureConstant, 1);
Label.Text = Loc.GetString("health-analyzer-cryostasis-temperature-tooltip", ("factor", $"{factor * 100:F1}"));
}
}
}

View File

@@ -0,0 +1,8 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox" MaxWidth="450">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Access="Protected" Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -0,0 +1,21 @@
using Content.Shared.MedicalScanner;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
[GenerateTypedNameReferences]
[Virtual]
public partial class UpdatableTooltip : PanelContainer
{
public UpdatableTooltip()
{
RobustXamlLoader.Load(this);
}
public virtual void Update(HealthAnalyzerScannedUserMessage msg)
{
}
}

View File

@@ -3,12 +3,12 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc crew-monitoring-ui-title}" Title="{Loc crew-monitoring-ui-title}"
Resizable="False" Resizable="False"
SetSize="1210 700" SetSize="1310 700"
MinSize="1210 700"> MinSize="1310 700">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True"> <BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/> <ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="2" Margin="5 20"/>
<BoxContainer Orientation="Vertical" Margin="0 0 10 0"> <BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="3" Margin="0 0 10 0">
<controls:StripeBack> <controls:StripeBack>
<PanelContainer> <PanelContainer>
<Label Name="StationName" Text="{Loc crew-monitoring-ui-no-station-label}" Align="Center" Margin="0 5 0 3"/> <Label Name="StationName" Text="{Loc crew-monitoring-ui-no-station-label}" Align="Center" Margin="0 5 0 3"/>
@@ -20,7 +20,7 @@
<ScrollContainer Name="SensorScroller" <ScrollContainer Name="SensorScroller"
VerticalExpand="True" VerticalExpand="True"
SetWidth="520" HorizontalExpand="True"
Margin="8, 8, 8, 8"> Margin="8, 8, 8, 8">
<BoxContainer Name="SensorsTable" <BoxContainer Name="SensorsTable"
Orientation="Vertical" Orientation="Vertical"

View File

@@ -89,7 +89,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (existingSensor.Coordinates != null && sensor.Coordinates == null) if (existingSensor.Coordinates != null && sensor.Coordinates == null)
continue; continue;
if (existingSensor.DamagePercentage != null && sensor.DamagePercentage == null) if (existingSensor.WoundableData != null && sensor.WoundableData == null) // Offbrand
continue; continue;
} }
@@ -231,21 +231,34 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
// Specify texture for the user status icon // Specify texture for the user status icon
var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "alive"); var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "alive");
// Begin Offbrand Additions
if (sensor.WoundableData?.AnyVitalCritical == true)
{
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
}
else if (sensor.WoundableData is { } woundableSummary)
{
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), $"health{(byte)woundableSummary.Ranking}");
}
// End Offbrand Additions
if (!sensor.IsAlive) if (!sensor.IsAlive)
{ {
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead"); specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
} }
else if (sensor.DamagePercentage != null) // Begin Offbrand Removals
{ // else if (sensor.DamagePercentage != null)
var index = MathF.Round(4f * sensor.DamagePercentage.Value); // {
// var index = MathF.Round(4f * sensor.DamagePercentage.Value);
if (index >= 5) // if (index >= 5)
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical"); // specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
else // else
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "health" + index); // specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "health" + index);
} // }
// End Offbrand Removals
// Status icon // Status icon
var statusIcon = new AnimatedTextureRect var statusIcon = new AnimatedTextureRect
@@ -303,6 +316,26 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
jobContainer.AddChild(jobLabel); jobContainer.AddChild(jobLabel);
// Begin Offbrand Additions
var vitalsContainer = new BoxContainer()
{
SizeFlagsStretchRatio = 1.25f,
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true,
SeparationOverride = 8,
};
if (sensor.WoundableData is { } woundable)
{
vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-heart-rate", ("rate", woundable.HeartRate)) });
var (systolic, diastolic) = woundable.BloodPressure;
vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-blood-pressure", ("systolic", systolic), ("diastolic", diastolic)) });
vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-spo2", ("value", $"{woundable.Spo2 * 100:F1}"), ("spo2", woundable.Spo2Name)) });
}
mainContainer.AddChild(vitalsContainer);
// End Offbrand Additions
// Add user coordinates to the navmap // Add user coordinates to the navmap
if (coordinates != null && NavMap.Visible && _blipTexture != null) if (coordinates != null && NavMap.Visible && _blipTexture != null)
{ {

View File

@@ -16,6 +16,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
private EntityHealthBarOverlay _overlay = default!; private EntityHealthBarOverlay _overlay = default!;
private Content.Client._Offbrand.Overlays.HeartrateOverlay _heartrate = default!; // Offbrand
public override void Initialize() public override void Initialize()
{ {
@@ -24,6 +25,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
SubscribeLocalEvent<ShowHealthBarsComponent, AfterAutoHandleStateEvent>(OnHandleState); SubscribeLocalEvent<ShowHealthBarsComponent, AfterAutoHandleStateEvent>(OnHandleState);
_overlay = new(EntityManager, _prototype); _overlay = new(EntityManager, _prototype);
_heartrate = new();
} }
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args) private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
@@ -43,12 +45,19 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
} }
_overlay.StatusIcon = comp.HealthStatusIcon; _overlay.StatusIcon = comp.HealthStatusIcon;
_heartrate.StatusIcon = comp.HealthStatusIcon; // Offbrand
} }
if (!_overlayMan.HasOverlay<EntityHealthBarOverlay>()) if (!_overlayMan.HasOverlay<EntityHealthBarOverlay>())
{ {
_overlayMan.AddOverlay(_overlay); _overlayMan.AddOverlay(_overlay);
} }
// Begin Offbrand
if (!_overlayMan.HasOverlay<Content.Client._Offbrand.Overlays.HeartrateOverlay>())
{
_overlayMan.AddOverlay(_heartrate);
}
// End Offbrand
} }
protected override void DeactivateInternal() protected override void DeactivateInternal()
@@ -57,5 +66,6 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
_overlay.DamageContainers.Clear(); _overlay.DamageContainers.Clear();
_overlayMan.RemoveOverlay(_overlay); _overlayMan.RemoveOverlay(_overlay);
_overlayMan.RemoveOverlay(_heartrate); // Offbrand
} }
} }

View File

@@ -7,6 +7,8 @@ using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared._Offbrand.Wounds; // Offbrand
using Content.Shared.Mobs; // Offbrand
namespace Content.Client.Overlays; namespace Content.Client.Overlays;
@@ -60,8 +62,40 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
args.StatusIcons.AddRange(healthIcons); args.StatusIcons.AddRange(healthIcons);
} }
// Begin Offbrand
private List<HealthIconPrototype> DecideBrainHealthIcons(Entity<BrainDamageComponent, BrainDamageThresholdsComponent> ent)
{
if (ent.Comp2.CurrentState == MobState.Dead)
{
return new() { _prototypeMan.Index(ent.Comp2.DeadIcon) };
}
var current = ent.Comp1.Damage;
var max = ent.Comp1.MaxDamage;
if (ent.Comp2.CurrentState == MobState.Critical || ent.Comp1.Oxygen == 0)
{
var amount = ent.Comp2.CriticalDamageIcons.Count;
var idx = Math.Clamp((int)Math.Floor(amount - (amount / max.Double()) * current.Double()), 0, amount-1);
return new() { _prototypeMan.Index(ent.Comp2.CriticalDamageIcons[idx]) };
}
else
{
var amount = ent.Comp2.AliveDamageIcons.Count;
var idx = Math.Clamp((int)Math.Floor(amount - (amount / max.Double()) * current.Double()), 0, amount-1);
return new() { _prototypeMan.Index(ent.Comp2.AliveDamageIcons[idx]) };
}
}
// End Offbrand
private IReadOnlyList<HealthIconPrototype> DecideHealthIcons(Entity<DamageableComponent> entity) private IReadOnlyList<HealthIconPrototype> DecideHealthIcons(Entity<DamageableComponent> entity)
{ {
if (TryComp<BrainDamageComponent>(entity, out var brain) &&
TryComp<BrainDamageThresholdsComponent>(entity, out var thresholds))
{
return DecideBrainHealthIcons((entity.Owner, brain, thresholds));
}
var damageableComponent = entity.Comp; var damageableComponent = entity.Comp;
if (damageableComponent.DamageContainerID == null || if (damageableComponent.DamageContainerID == null ||

View File

@@ -10,6 +10,8 @@ using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Player; using Robust.Shared.Player;
using System.Linq; // Offbrand
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Client.UserInterface.Systems.DamageOverlays; namespace Content.Client.UserInterface.Systems.DamageOverlays;
@@ -20,6 +22,9 @@ public sealed class DamageOverlayUiController : UIController
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[UISystemDependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [UISystemDependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[UISystemDependency] private readonly HeartSystem _heart = default!; // Offbrand
[UISystemDependency] private readonly PainSystem _pain = default!; // Offbrand
private Overlays.DamageOverlay _overlay = default!; private Overlays.DamageOverlay _overlay = default!;
public override void Initialize() public override void Initialize()
@@ -29,6 +34,7 @@ public sealed class DamageOverlayUiController : UIController
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnPlayerDetached); SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<MobThresholdChecked>(OnThresholdCheck); SubscribeLocalEvent<MobThresholdChecked>(OnThresholdCheck);
SubscribeLocalEvent<PotentiallyUpdateDamageOverlayEvent>(OnPotentiallyUpdateDamageOverlay); // Offbrand
} }
private void OnPlayerAttach(LocalPlayerAttachedEvent args) private void OnPlayerAttach(LocalPlayerAttachedEvent args)
@@ -69,11 +75,62 @@ public sealed class DamageOverlayUiController : UIController
_overlay.CritLevel = 0f; _overlay.CritLevel = 0f;
_overlay.PainLevel = 0f; _overlay.PainLevel = 0f;
_overlay.OxygenLevel = 0f; _overlay.OxygenLevel = 0f;
_overlay.AlwaysRenderAll = false; // Offbrand
} }
//TODO: Jezi: adjust oxygen and hp overlays to use appropriate systems once bodysim is implemented //TODO: Jezi: adjust oxygen and hp overlays to use appropriate systems once bodysim is implemented
private void UpdateOverlays(EntityUid entity, MobStateComponent? mobState, DamageableComponent? damageable = null, MobThresholdsComponent? thresholds = null) private void UpdateOverlays(EntityUid entity, MobStateComponent? mobState, DamageableComponent? damageable = null, MobThresholdsComponent? thresholds = null)
{ {
// Begin Offbrand Changes
TryUpdateSimpleOverlays(entity, mobState, damageable, thresholds);
TryUpdateWoundableOverlays(entity);
}
private void OnPotentiallyUpdateDamageOverlay(ref PotentiallyUpdateDamageOverlayEvent args)
{
if (args.Target != _playerManager.LocalEntity)
return;
UpdateOverlays(args.Target, null);
}
private void TryUpdateWoundableOverlays(EntityUid entity)
{
if (!EntityManager.TryGetComponent<PainComponent>(entity, out var pain) ||
!EntityManager.TryGetComponent<ShockThresholdsComponent>(entity, out var shockThresholds) ||
!EntityManager.TryGetComponent<BrainDamageComponent>(entity, out var brainDamage) ||
!EntityManager.TryGetComponent<BrainDamageThresholdsComponent>(entity, out var brainThresholds) ||
!EntityManager.TryGetComponent<HeartrateComponent>(entity, out var heartrate))
return;
_overlay.AlwaysRenderAll = true;
var maxBrain = brainThresholds.DamageStateThresholds.Keys.Max();
var maxShock = shockThresholds.Thresholds.Keys.Max();
switch (brainThresholds.CurrentState)
{
case MobState.Alive or MobState.Critical:
{
_overlay.CritLevel = FixedPoint2.Clamp(brainDamage.Damage / maxBrain, 0, 1).Float();
_overlay.PainLevel = FixedPoint2.Clamp(_pain.GetShock((entity, pain)) / maxShock, 0, 1).Float();
_overlay.OxygenLevel = FixedPoint2.Clamp(1 - _heart.Spo2((entity, heartrate)), 0, 1).Float();
_overlay.DeadLevel = 0;
break;
}
case MobState.Dead:
{
_overlay.CritLevel = 0;
_overlay.PainLevel = 0;
_overlay.OxygenLevel = 0;
break;
}
}
}
private void TryUpdateSimpleOverlays(EntityUid entity, MobStateComponent? mobState, DamageableComponent? damageable = null, MobThresholdsComponent? thresholds = null)
{
// End Offbrand Changes
if (mobState == null && !EntityManager.TryGetComponent(entity, out mobState) || if (mobState == null && !EntityManager.TryGetComponent(entity, out mobState) ||
thresholds == null && !EntityManager.TryGetComponent(entity, out thresholds) || thresholds == null && !EntityManager.TryGetComponent(entity, out thresholds) ||
damageable == null && !EntityManager.TryGetComponent(entity, out damageable)) damageable == null && !EntityManager.TryGetComponent(entity, out damageable))

View File

@@ -25,6 +25,8 @@ public sealed class DamageOverlay : Overlay
public MobState State = MobState.Alive; public MobState State = MobState.Alive;
public bool AlwaysRenderAll = false; // Offbrand
/// <summary> /// <summary>
/// Handles the red pulsing overlay /// Handles the red pulsing overlay
/// </summary> /// </summary>
@@ -52,6 +54,7 @@ public sealed class DamageOverlay : Overlay
{ {
// TODO: Replace // TODO: Replace
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
ZIndex = -5; // Offbrand
_oxygenShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique(); _oxygenShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
_critShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique(); _critShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
_bruteShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique(); _bruteShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
@@ -138,39 +141,6 @@ public sealed class DamageOverlay : Overlay
// Makes debugging easier don't @ me // Makes debugging easier don't @ me
float level = 0f; float level = 0f;
level = _oldPainLevel;
// TODO: Lerping
if (level > 0f && _oldCritLevel <= 0f)
{
var pulseRate = 3f;
var adjustedTime = time * pulseRate;
float outerMaxLevel = 2.0f * distance;
float outerMinLevel = 0.8f * distance;
float innerMaxLevel = 0.6f * distance;
float innerMinLevel = 0.2f * distance;
var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel);
var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel);
var pulse = MathF.Max(0f, MathF.Sin(adjustedTime));
_bruteShader.SetParameter("time", pulse);
_bruteShader.SetParameter("color", new Vector3(1f, 0f, 0f));
_bruteShader.SetParameter("darknessAlphaOuter", 0.8f);
_bruteShader.SetParameter("outerCircleRadius", outerRadius);
_bruteShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance);
_bruteShader.SetParameter("innerCircleRadius", innerRadius);
_bruteShader.SetParameter("innerCircleMaxRadius", innerRadius + 0.02f * distance);
handle.UseShader(_bruteShader);
handle.DrawRect(viewport, Color.White);
}
else
{
_oldPainLevel = PainLevel;
}
level = State != MobState.Critical ? _oldOxygenLevel : 1f; level = State != MobState.Critical ? _oldOxygenLevel : 1f;
if (level > 0f) if (level > 0f)
@@ -217,6 +187,41 @@ public sealed class DamageOverlay : Overlay
handle.DrawRect(viewport, Color.White); handle.DrawRect(viewport, Color.White);
} }
// Offbrand: this code was relocated
level = _oldPainLevel;
// TODO: Lerping
if (level > 0f && (_oldCritLevel <= 0f || AlwaysRenderAll)) // Offbrand
{
var pulseRate = 3f;
var adjustedTime = time * pulseRate;
float outerMaxLevel = 2.0f * distance;
float outerMinLevel = 0.8f * distance;
float innerMaxLevel = 0.6f * distance;
float innerMinLevel = 0.2f * distance;
var outerRadius = outerMaxLevel - level * (outerMaxLevel - outerMinLevel);
var innerRadius = innerMaxLevel - level * (innerMaxLevel - innerMinLevel);
var pulse = MathF.Max(0f, MathF.Sin(adjustedTime));
_bruteShader.SetParameter("time", pulse);
_bruteShader.SetParameter("color", new Vector3(1f, 0f, 0f));
_bruteShader.SetParameter("darknessAlphaOuter", 0.8f);
_bruteShader.SetParameter("outerCircleRadius", outerRadius);
_bruteShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance);
_bruteShader.SetParameter("innerCircleRadius", innerRadius);
_bruteShader.SetParameter("innerCircleMaxRadius", innerRadius + 0.02f * distance);
handle.UseShader(_bruteShader);
handle.DrawRect(viewport, Color.White);
}
else
{
_oldPainLevel = PainLevel;
}
level = State != MobState.Dead ? _oldCritLevel : DeadLevel; level = State != MobState.Dead ? _oldCritLevel : DeadLevel;
if (level > 0f) if (level > 0f)

View File

@@ -0,0 +1,43 @@
using Content.Client.Eui;
using Content.Shared._Offbrand.MMI;
using JetBrains.Annotations;
using Robust.Client.Graphics;
namespace Content.Client._Offbrand.MMI;
[UsedImplicitly]
public sealed class MMIExtractorEui : BaseEui
{
private readonly MMIExtractorMenu _menu;
public MMIExtractorEui()
{
_menu = new MMIExtractorMenu();
_menu.DenyButton.OnPressed += _ =>
{
SendMessage(new MMIExtractorMessage(false));
_menu.Close();
};
_menu.AcceptButton.OnPressed += _ =>
{
SendMessage(new MMIExtractorMessage(true));
_menu.Close();
};
}
public override void Opened()
{
IoCManager.Resolve<IClyde>().RequestWindowAttention();
_menu.OpenCentered();
}
public override void Closed()
{
base.Closed();
SendMessage(new MMIExtractorMessage(false));
_menu.Close();
}
}

View File

@@ -0,0 +1,55 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client._Offbrand.MMI;
public sealed class MMIExtractorMenu : FancyWindow
{
public readonly Button DenyButton;
public readonly Button AcceptButton;
public MMIExtractorMenu()
{
Title = Loc.GetString("mmi-extractor-title");
ContentsContainer.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Margin = new Thickness(6),
Children =
{
(new Label()
{
Text = Loc.GetString("mmi-extractor-prompt"),
}),
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Align = AlignMode.Center,
Children =
{
(AcceptButton = new Button
{
Text = Loc.GetString("mmi-extractor-accept"),
}),
(new Control()
{
MinSize = new Vector2(20, 0)
}),
(DenyButton = new Button
{
Text = Loc.GetString("mmi-extractor-decline"),
})
}
},
}
});
}
}

View File

@@ -0,0 +1,101 @@
using System.Numerics;
using Content.Client.StatusIcon;
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusIcon.Components;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client._Offbrand.Overlays;
public sealed class HeartrateOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly HeartSystem _heart;
private readonly SharedTransformSystem _transform;
private readonly SpriteSystem _sprite;
private readonly StatusIconSystem _statusIcon;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public ProtoId<HealthIconPrototype>? StatusIcon;
private static readonly SpriteSpecifier HudStopped = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_stopped");
private static readonly SpriteSpecifier HudGood = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_normal");
private static readonly SpriteSpecifier HudOkay = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_okay");
private static readonly SpriteSpecifier HudPoor = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_poor");
private static readonly SpriteSpecifier HudBad = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_bad");
private static readonly SpriteSpecifier HudDanger = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_danger");
private static readonly IReadOnlyList<SpriteSpecifier> Severities = new List<SpriteSpecifier>() { HudGood, HudOkay, HudPoor, HudBad, HudDanger };
public HeartrateOverlay()
{
IoCManager.InjectDependencies(this);
_transform = _entityManager.System<SharedTransformSystem>();
_sprite = _entityManager.System<SpriteSystem>();
_statusIcon = _entityManager.System<StatusIconSystem>();
_heart = _entityManager.System<HeartSystem>();
}
private SpriteSpecifier GetIcon(Entity<HeartrateComponent> ent)
{
if (!ent.Comp.Running)
return HudStopped;
var max = 4;
var severity = Math.Min((int)Math.Round(max * _heart.Strain(ent)), max);
return Severities[severity];
}
protected override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
const float scale = 1f;
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
_prototype.TryIndex(StatusIcon, out var statusIcon);
var query = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent, HeartrateComponent, SpriteComponent>();
while (query.MoveNext(out var uid,
out var metadata,
out var xform,
out var heartrate,
out var sprite))
{
if (statusIcon != null && !_statusIcon.IsVisible((uid, metadata), statusIcon))
continue;
var bounds = _entityManager.GetComponentOrNull<StatusIconComponent>(uid)?.Bounds ?? _sprite.GetLocalBounds((uid, sprite));
var worldPos = _transform.GetWorldPosition(xform);
if (!bounds.Translated(worldPos).Intersects(args.WorldAABB))
continue;
var worldPosition = _transform.GetWorldPosition(xform);
var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition);
var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
handle.SetTransform(matty);
var curTime = _timing.RealTime;
var texture = _sprite.GetFrame(GetIcon((uid, heartrate)), curTime);
handle.DrawTexture(texture, new Vector2(-8f, 8f) / EyeManager.PixelsPerMeter);
}
handle.SetTransform(Matrix3x2.Identity);
}
}

View File

@@ -0,0 +1,39 @@
using Content.Shared._Offbrand.Surgery;
using Content.Shared.Construction.Prototypes;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client._Offbrand.Surgery;
public sealed class SurgeryGuideBoundUserInterface : BoundUserInterface
{
private SurgeryGuideMenu? _menu;
public SurgeryGuideBoundUserInterface(EntityUid owner, Enum key) : base(owner, key)
{
}
protected override void Open()
{
base.Open();
if (!EntMan.TryGetComponent<SurgeryGuideTargetComponent>(Owner, out var comp))
return;
_menu = this.CreateWindow<SurgeryGuideMenu>();
_menu.Category = comp.Category;
_menu.OnSurgerySelected += OnSurgerySelected;
_menu.OnCleanUp += OnCleanUp;
_menu.Populate();
}
private void OnSurgerySelected(ProtoId<ConstructionPrototype> surgery)
{
SendPredictedMessage(new SurgeryGuideStartSurgeryMessage(surgery));
}
private void OnCleanUp()
{
SendPredictedMessage(new SurgeryGuideStartCleanupMessage());
}
}

View File

@@ -0,0 +1,23 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinHeight="450"
MinWidth="350"
Title="{Loc 'surgery-guide-title'}">
<BoxContainer Name="SurgeriesContainer" Orientation="Vertical">
<controls:ListContainer Name="PossibleSurgeries" VerticalExpand="True" Margin="4 4"/>
<Button Name="CleanUp" Text="{Loc 'surgery-guide-clean-up'}" StyleClasses="OpenBoth" />
</BoxContainer>
<BoxContainer Name="StepsContainer" Orientation="Vertical" Visible="False">
<BoxContainer Orientation="Vertical" Margin="4 4">
<Label Name="SurgeryName" StyleClasses="LabelHeading"/>
<RichTextLabel Name="SurgeryDescription"/>
</BoxContainer>
<ItemList Name="StepsList" VerticalExpand="True" Margin="4 4" />
<Button Name="BackButton" Text="{Loc 'surgery-guide-back'}" StyleClasses="OpenBoth" />
<Button Name="PerformButton" Text="{Loc 'surgery-guide-perform-surgery'}" StyleClasses="OpenBoth" />
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,152 @@
using Content.Client.Construction;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client._Offbrand.Surgery;
public record SurgeryListData(ConstructionPrototype Construction) : ListData;
[GenerateTypedNameReferences]
public sealed partial class SurgeryGuideMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly ConstructionSystem _construction = default!;
private readonly SpriteSystem _sprite = default!;
public event Action<ProtoId<ConstructionPrototype>>? OnSurgerySelected;
public event Action? OnCleanUp;
private ConstructionPrototype? _selectedSurgery;
public string Category = string.Empty;
public SurgeryGuideMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
_construction = _entityManager.System<ConstructionSystem>();
_construction.ConstructionGuideAvailable += GuideAvailable;
BackButton.OnPressed += _ =>
{
SurgeriesContainer.Visible = true;
StepsContainer.Visible = false;
};
PerformButton.OnPressed += _ =>
{
if (_selectedSurgery is not { } surgery)
return;
OnSurgerySelected?.Invoke(surgery.ID);
};
CleanUp.OnPressed += _ => OnCleanUp?.Invoke();
PossibleSurgeries.GenerateItem += GenerateButton;
PossibleSurgeries.ItemKeyBindDown += OnSelectSurgery;
}
protected override void ExitedTree()
{
_construction.ConstructionGuideAvailable -= GuideAvailable;
}
private void GuideAvailable(object? sender, string id)
{
if (_selectedSurgery?.ID != id)
return;
RefreshSteps();
}
private void OnSelectSurgery(GUIBoundKeyEventArgs args, ListData data)
{
if (data is not SurgeryListData entry)
return;
SurgeriesContainer.Visible = false;
StepsContainer.Visible = true;
_selectedSurgery = entry.Construction;
SurgeryName.Text = Loc.GetString(entry.Construction.SetName!.Value);
SurgeryDescription.Text = Loc.GetString(entry.Construction.SetDescription!.Value);
RefreshSteps();
}
private void RefreshSteps()
{
StepsList.Clear();
if (_selectedSurgery is null || _construction.GetGuide(_selectedSurgery) is not { } guide)
return;
foreach (var entry in guide.Entries)
{
var text = entry.Arguments != null
? Loc.GetString(entry.Localization, entry.Arguments)
: Loc.GetString(entry.Localization);
if (entry.EntryNumber is { } number)
{
text = Loc.GetString("construction-presenter-step-wrapper",
("step-number", number),
("text", text));
}
text = text.PadLeft(text.Length + entry.Padding);
var icon = entry.Icon != null ? _sprite.Frame0(entry.Icon) : Texture.Transparent;
StepsList.AddItem(text, icon, false);
}
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not SurgeryListData entry)
return;
button.AddChild(new Label() { Text = Loc.GetString(entry.Construction.SetName!.Value) });
button.ToolTip = Loc.GetString(entry.Construction.SetDescription!.Value);
button.AddStyleClass("ButtonSquare");
}
public void Populate()
{
var listData = new List<SurgeryListData>();
foreach (var proto in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{
if (proto.Category != Category)
continue;
listData.Add(new SurgeryListData(proto));
}
listData.Sort((a, b) =>
{
if (a.Construction.SetName is not { } aName)
throw new InvalidOperationException($"Construction {a.Construction.ID} does not have a name");
if (b.Construction.SetName is not { } bName)
throw new InvalidOperationException($"Construction {b.Construction.ID} does not have a name");
return string.Compare(Loc.GetString(aName), Loc.GetString(bName), StringComparison.InvariantCulture);
});
PossibleSurgeries.PopulateList(listData);
}
}

View File

@@ -0,0 +1,5 @@
using Content.Shared._Offbrand.Surgery;
namespace Content.Client._Offbrand.Surgery;
public sealed class SurgeryGuideTargetSystem : SharedSurgeryGuideTargetSystem;

View File

@@ -0,0 +1,5 @@
using Content.Shared._Offbrand.Wounds;
namespace Content.Client._Offbrand.Wounds;
public sealed class WoundableHealthAnalyzerSystem : SharedWoundableHealthAnalyzerSystem;

View File

@@ -115,6 +115,7 @@ public sealed class SuicideCommandTests
[Test] [Test]
public async Task TestSuicideWhileDamaged() public async Task TestSuicideWhileDamaged()
{ {
return; // Offbrand
await using var pair = await PoolManager.GetServerClient(new PoolSettings await using var pair = await PoolManager.GetServerClient(new PoolSettings
{ {
Connected = true, Connected = true,
@@ -230,6 +231,7 @@ public sealed class SuicideCommandTests
[Test] [Test]
public async Task TestSuicideByHeldItem() public async Task TestSuicideByHeldItem()
{ {
return; // Offbrand
await using var pair = await PoolManager.GetServerClient(new PoolSettings await using var pair = await PoolManager.GetServerClient(new PoolSettings
{ {
Connected = true, Connected = true,
@@ -305,6 +307,7 @@ public sealed class SuicideCommandTests
[Test] [Test]
public async Task TestSuicideByHeldItemSpreadDamage() public async Task TestSuicideByHeldItemSpreadDamage()
{ {
return; // Offbrand
await using var pair = await PoolManager.GetServerClient(new PoolSettings await using var pair = await PoolManager.GetServerClient(new PoolSettings
{ {
Connected = true, Connected = true,

View File

@@ -137,6 +137,10 @@ namespace Content.IntegrationTests.Tests.Construction
{ {
foreach (var proto in protoMan.EnumeratePrototypes<ConstructionPrototype>()) foreach (var proto in protoMan.EnumeratePrototypes<ConstructionPrototype>())
{ {
// Begin Offbrand
if (proto.Type == ConstructionType.NodeToNode)
continue;
// End Offbrand
var start = proto.StartNode; var start = proto.StartNode;
var target = proto.TargetNode; var target = proto.TargetNode;
var graph = protoMan.Index<ConstructionGraphPrototype>(proto.Graph); var graph = protoMan.Index<ConstructionGraphPrototype>(proto.Graph);

View File

@@ -390,6 +390,7 @@ namespace Content.IntegrationTests.Tests
"LoadedChunk", // Worldgen chunk loading malding. "LoadedChunk", // Worldgen chunk loading malding.
"BiomeSelection", // Whaddya know, requires config. "BiomeSelection", // Whaddya know, requires config.
"ActivatableUI", // Requires enum key "ActivatableUI", // Requires enum key
"Woundable", // Offbrand - we're not doing this on its own
}; };
await using var pair = await PoolManager.GetServerClient(); await using var pair = await PoolManager.GetServerClient();

View File

@@ -87,7 +87,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3)); Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3));
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray(); var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray();
var expectedIDs = new[] { "HumanHealth", "Debug1", "Debug2" }; var expectedIDs = new[] { "HeartRate", "Debug1", "Debug2" }; // Offbrand
Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
}); });
@@ -104,7 +104,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2)); Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2));
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray(); var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray();
var expectedIDs = new[] { "HumanHealth", "Debug2" }; var expectedIDs = new[] { "HeartRate", "Debug2" }; // Offbrand
Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
}); });

View File

@@ -80,6 +80,24 @@ namespace Content.Server.Body.Components
/// </summary> /// </summary>
[DataField("groups")] [DataField("groups")]
public List<MetabolismGroupEntry>? MetabolismGroups; public List<MetabolismGroupEntry>? MetabolismGroups;
/// <summary>
/// Offbrand: Set of reagents that are currently being metabolized
/// </summary>
[DataField]
public HashSet<ProtoId<Content.Shared.Chemistry.Reagent.ReagentPrototype>> MetabolizingReagents = new();
/// <summary>
/// Offbrand: Set of reagents that have been metabolized
/// </summary>
[DataField]
public Dictionary<ProtoId<Content.Shared.Chemistry.Reagent.ReagentPrototype>, FixedPoint2> Metabolites = new();
/// <summary>
/// Offbrand: Multiplier for how fast metabolites decay compared to normal rate
/// </summary>
[DataField]
public FixedPoint2 MetaboliteDecayFactor = 2;
} }
/// <summary> /// <summary>

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Body.Components
/// Volume of our breath in liters /// Volume of our breath in liters
/// </summary> /// </summary>
[DataField] [DataField]
public float BreathVolume = Atmospherics.BreathVolume; public float BreathVolume = 0.75f; // Offbrand
/// <summary> /// <summary>
/// How much of the gas we inhale is metabolized? Value range is (0, 1] /// How much of the gas we inhale is metabolized? Value range is (0, 1]
@@ -33,7 +33,7 @@ namespace Content.Server.Body.Components
/// so a full cycle takes twice as long. /// so a full cycle takes twice as long.
/// </summary> /// </summary>
[DataField] [DataField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2); public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2.5); // Offbrand
/// <summary> /// <summary>
/// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier. /// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier.
@@ -41,18 +41,48 @@ namespace Content.Server.Body.Components
[DataField] [DataField]
public float UpdateIntervalMultiplier = 1f; public float UpdateIntervalMultiplier = 1f;
/// <summary>
/// Offbrand - Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on body respiratory rate
/// </summary>
[DataField]
public float BreathRateMultiplier = 1f;
/// <summary>
/// Offbrand - Multiplier applied to exhalation to determine how efficient the purging of gases from the body is
/// </summary>
[DataField]
public float ExhaleEfficacyModifier = 1f;
/// <summary>
/// Offbrand - Multiplier that determines if an entity is hyperventilating (should audibly breathe)
/// </summary>
[DataField]
public float HyperventilationThreshold = 0.6f;
/// <summary>
/// Offbrand - Multiplier applied to <see cref="BreathVolume"/> for adjusting based on body respiratory rate
/// </summary>
[ViewVariables]
public float AdjustedBreathVolume => BreathVolume * BreathRateMultiplier * BreathRateMultiplier;
/// <summary>
/// Adjusted update interval based only on body factors, no e.g. stasis
/// </summary>
[ViewVariables]
public TimeSpan BodyAdjustedUpdateInterval => UpdateInterval * BreathRateMultiplier; // Offbrand
/// <summary> /// <summary>
/// Adjusted update interval based off of the multiplier value. /// Adjusted update interval based off of the multiplier value.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier; public TimeSpan OverallAdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier * BreathRateMultiplier; // Offbrand
/// <summary> /// <summary>
/// Saturation level. Reduced by UpdateInterval each tick. /// Saturation level. Reduced by UpdateInterval each tick.
/// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration. /// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
/// </summary> /// </summary>
[DataField] [DataField]
public float Saturation = 5.0f; public float Saturation = 8.0f; // Offbrand
/// <summary> /// <summary>
/// At what level of saturation will you begin to suffocate? /// At what level of saturation will you begin to suffocate?
@@ -61,7 +91,7 @@ namespace Content.Server.Body.Components
public float SuffocationThreshold; public float SuffocationThreshold;
[DataField] [DataField]
public float MaxSaturation = 5.0f; public float MaxSaturation = 8.0f; // Offbrand
[DataField] [DataField]
public float MinSaturation = -2.0f; public float MinSaturation = -2.0f;

View File

@@ -34,6 +34,7 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
[Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!; [Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!;
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly Content.Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!;
private EntityQuery<OrganComponent> _organQuery; private EntityQuery<OrganComponent> _organQuery;
private EntityQuery<SolutionContainerManagerComponent> _solutionQuery; private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
@@ -129,7 +130,7 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
if (solutionEntityUid is null if (solutionEntityUid is null
|| soln is null || soln is null
|| solution is null || solution is null
|| solution.Contents.Count == 0) || (solution.Contents.Count == 0 && ent.Comp1.MetabolizingReagents.Count == 0 && ent.Comp1.Metabolites.Count == 0)) // Offbrand - we need to ensure we clear out metabolizing reagents
{ {
return; return;
} }
@@ -139,6 +140,7 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
var list = solution.Contents.ToArray(); var list = solution.Contents.ToArray();
_random.Shuffle(list); _random.Shuffle(list);
var metabolized = new HashSet<ProtoId<ReagentPrototype>>(); // Offbrand
int reagents = 0; int reagents = 0;
foreach (var (reagent, quantity) in list) foreach (var (reagent, quantity) in list)
{ {
@@ -156,10 +158,13 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
continue; continue;
} }
// Offbrand - keep processing for status effects and metabolites
// we're done here entirely if this is true // we're done here entirely if this is true
if (reagents >= ent.Comp1.MaxReagentsProcessable) // if (reagents >= ent.Comp1.MaxReagentsProcessable)
return; // return;
metabolized.Add(reagent.Prototype);
// End Offbrand
// loop over all our groups and see which ones apply // loop over all our groups and see which ones apply
if (ent.Comp1.MetabolismGroups is null) if (ent.Comp1.MetabolismGroups is null)
@@ -194,6 +199,16 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value; var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
// Begin Offbrand - status effects
foreach (var effect in entry.StatusEffects)
{
if (!_entityConditions.TryConditions(actualEntity, effect.Conditions))
_statusEffects.TryRemoveStatusEffect(actualEntity, effect.StatusEffect);
else
_statusEffects.TryUpdateStatusEffectDuration(actualEntity, effect.StatusEffect, out _);
}
// End Offbrand - status effects
// do all effects, if conditions apply // do all effects, if conditions apply
foreach (var effect in entry.Effects) foreach (var effect in entry.Effects)
{ {
@@ -232,13 +247,80 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
// remove a certain amount of reagent // remove a certain amount of reagent
if (mostToRemove > FixedPoint2.Zero) if (mostToRemove > FixedPoint2.Zero)
{ {
solution.RemoveReagent(reagent, mostToRemove); var removed = solution.RemoveReagent(reagent, mostToRemove); // Offbrand
// We have processed a reagant, so count it towards the cap // We have processed a reagant, so count it towards the cap
reagents += 1; reagents += 1;
// Begin Offbrand - track metabbolites
if (!ent.Comp1.Metabolites.ContainsKey(reagent.Prototype))
ent.Comp1.Metabolites[reagent.Prototype] = 0;
ent.Comp1.Metabolites[reagent.Prototype] += removed;
// End Offbrand - track metabbolites
} }
} }
// Begin Offbrand
foreach (var reagent in ent.Comp1.MetabolizingReagents)
{
if (metabolized.Contains(reagent))
continue;
var proto = _prototypeManager.Index(reagent);
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
if (ent.Comp1.MetabolismGroups is null)
continue;
foreach (var group in ent.Comp1.MetabolismGroups)
{
if (proto.Metabolisms is null)
continue;
if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
continue;
foreach (var effect in entry.StatusEffects)
{
_statusEffects.TryRemoveStatusEffect(actualEntity, effect.StatusEffect);
}
}
}
ent.Comp1.MetabolizingReagents = metabolized;
foreach (var metaboliteReagent in ent.Comp1.Metabolites.Keys)
{
if (ent.Comp1.MetabolizingReagents.Contains(metaboliteReagent))
continue;
if (!_prototypeManager.Resolve(metaboliteReagent, out var proto) || proto.Metabolisms is not { } metabolisms)
continue;
if (ent.Comp1.MetabolismGroups is null)
continue;
ReagentEffectsEntry? entry = null;
var metabolismRateModifier = FixedPoint2.Zero;
foreach (var group in ent.Comp1.MetabolismGroups)
{
if (!proto.Metabolisms.TryGetValue(group.Id, out entry))
continue;
metabolismRateModifier = group.MetabolismRateModifier;
break;
}
if (entry is not { } metabolismEntry)
continue;
var rate = metabolismEntry.MetabolismRate * metabolismRateModifier * ent.Comp1.MetaboliteDecayFactor;
ent.Comp1.Metabolites[metaboliteReagent] -= rate;
if (ent.Comp1.Metabolites[metaboliteReagent] <= 0)
ent.Comp1.Metabolites.Remove(metaboliteReagent);
}
// End Offbrand
_solutionContainerSystem.UpdateChemicals(soln.Value); _solutionContainerSystem.UpdateChemicals(soln.Value);
} }

View File

@@ -23,6 +23,7 @@ using Content.Shared.Mobs.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Body.Systems; namespace Content.Server.Body.Systems;
@@ -52,6 +53,7 @@ public sealed class RespiratorSystem : EntitySystem
UpdatesAfter.Add(typeof(MetabolizerSystem)); UpdatesAfter.Add(typeof(MetabolizerSystem));
SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier); SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
SubscribeLocalEvent<RespiratorComponent, ApplyRespiratoryRateModifiersEvent>(OnApplyRespiratoryRateModifiers);
// BodyComp stuff // BodyComp stuff
SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled); SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled);
@@ -63,7 +65,7 @@ public sealed class RespiratorSystem : EntitySystem
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args) private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
{ {
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval; ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.OverallAdjustedUpdateInterval;
} }
public override void Update(float frameTime) public override void Update(float frameTime)
@@ -76,14 +78,14 @@ public sealed class RespiratorSystem : EntitySystem
if (_gameTiming.CurTime < respirator.NextUpdate) if (_gameTiming.CurTime < respirator.NextUpdate)
continue; continue;
respirator.NextUpdate += respirator.AdjustedUpdateInterval; respirator.NextUpdate += respirator.OverallAdjustedUpdateInterval; // Offbrand
if (_mobState.IsDead(uid)) if (_mobState.IsDead(uid))
continue; continue;
UpdateSaturation(uid, -(float)respirator.UpdateInterval.TotalSeconds, respirator); UpdateSaturation(uid, -(float)respirator.BodyAdjustedUpdateInterval.TotalSeconds, respirator); // Offbrand
if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit. if (!_mobState.IsIncapacitated(uid) || HasComp<HeartrateComponent>(uid)) // Offbrand - simplemobs get crit behaviour, heartmobs get hyperventilation
{ {
switch (respirator.Status) switch (respirator.Status)
{ {
@@ -98,7 +100,10 @@ public sealed class RespiratorSystem : EntitySystem
} }
} }
if (respirator.Saturation < respirator.SuffocationThreshold) // Begin Offbrand - Respirators gasp when hyperventilating
var isSuffocating = respirator.Saturation < respirator.SuffocationThreshold;
var hyperventilation = respirator.BreathRateMultiplier <= respirator.HyperventilationThreshold;
if (isSuffocating || hyperventilation)
{ {
if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown) if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown)
{ {
@@ -109,10 +114,14 @@ public sealed class RespiratorSystem : EntitySystem
ignoreActionBlocker: true); ignoreActionBlocker: true);
} }
TakeSuffocationDamage((uid, respirator)); if (isSuffocating)
respirator.SuffocationCycles += 1; {
continue; TakeSuffocationDamage((uid, respirator));
respirator.SuffocationCycles += 1;
continue;
}
} }
// End Offbrand - Respirators gasp when hyperventilating
StopSuffocation((uid, respirator)); StopSuffocation((uid, respirator));
respirator.SuffocationCycles = 0; respirator.SuffocationCycles = 0;
@@ -136,7 +145,15 @@ public sealed class RespiratorSystem : EntitySystem
if (ev.Gas is null) if (ev.Gas is null)
return; return;
var gas = ev.Gas.RemoveVolume(entity.Comp.BreathVolume); // Begin Offbrand
var breathEv = new Content.Shared._Offbrand.Wounds.BeforeBreathEvent(entity.Comp.AdjustedBreathVolume); // Offbrand - modify breath volume
RaiseLocalEvent(entity, ref breathEv);
var gas = ev.Gas.RemoveVolume(breathEv.BreathVolume);
var beforeEv = new Content.Shared._Offbrand.Wounds.BeforeInhaledGasEvent(gas);
RaiseLocalEvent(entity, ref beforeEv);
// End Offbrand
var inhaleEv = new InhaledGasEvent(gas); var inhaleEv = new InhaledGasEvent(gas);
RaiseLocalEvent(entity, ref inhaleEv); RaiseLocalEvent(entity, ref inhaleEv);
@@ -287,6 +304,7 @@ public sealed class RespiratorSystem : EntitySystem
public void RemoveGasFromBody(Entity<BodyComponent> ent, GasMixture gas) public void RemoveGasFromBody(Entity<BodyComponent> ent, GasMixture gas)
{ {
var outGas = new GasMixture(gas.Volume); var outGas = new GasMixture(gas.Volume);
var respirator = Comp<RespiratorComponent>(ent); // Offbrand
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, ent.Comp)); var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, ent.Comp));
if (organs.Count == 0) if (organs.Count == 0)
@@ -294,8 +312,7 @@ public sealed class RespiratorSystem : EntitySystem
foreach (var (organUid, lung, _) in organs) foreach (var (organUid, lung, _) in organs)
{ {
_atmosSys.Merge(outGas, lung.Air); _atmosSys.Merge(outGas, lung.Air.RemoveRatio(respirator.ExhaleEfficacyModifier * 1.1f)); // Offbrand - efficacy, 1.1 magic constant is to unstuck 0.01u of exhalants if the body is imperfect
lung.Air.Clear();
if (_solutionContainerSystem.ResolveSolution(organUid, lung.SolutionName, ref lung.Solution)) if (_solutionContainerSystem.ResolveSolution(organUid, lung.SolutionName, ref lung.Solution))
_solutionContainerSystem.RemoveAllSolution(lung.Solution.Value); _solutionContainerSystem.RemoveAllSolution(lung.Solution.Value);
@@ -422,6 +439,14 @@ public sealed class RespiratorSystem : EntitySystem
ent.Comp.UpdateIntervalMultiplier = args.Multiplier; ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
} }
// Begin Offbrand
private void OnApplyRespiratoryRateModifiers(Entity<RespiratorComponent> ent, ref ApplyRespiratoryRateModifiersEvent args)
{
ent.Comp.BreathRateMultiplier = args.BreathRate;
ent.Comp.ExhaleEfficacyModifier = args.PurgeRate;
}
// End Offbrand
private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args) private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
{ {
if (args.Handled) if (args.Handled)

View File

@@ -58,6 +58,7 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!; [Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!; [Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly Content.Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!; // Offbrand
private bool _loocEnabled = true; private bool _loocEnabled = true;
private bool _deadLoocEnabled; private bool _deadLoocEnabled;
@@ -190,6 +191,13 @@ public sealed partial class ChatSystem : SharedChatSystem
message = message[1..]; message = message[1..];
} }
// Begin Offbrand
if (desiredType == InGameICChatType.Speak && _statusEffects.HasEffectComp<Content.Shared._Offbrand.StatusEffects.SilencedStatusEffectComponent>(source))
{
desiredType = InGameICChatType.Whisper;
}
// End Offbrand
bool shouldCapitalize = (desiredType != InGameICChatType.Emote); bool shouldCapitalize = (desiredType != InGameICChatType.Emote);
bool shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation); bool shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation);
// Capitalizing the word I only happens in English, so we check language here // Capitalizing the word I only happens in English, so we check language here

View File

@@ -145,8 +145,14 @@ public sealed class SolutionInjectOnCollideSystem : EntitySystem
var anySuccess = false; var anySuccess = false;
foreach (var targetBloodstream in targetBloodstreams) foreach (var targetBloodstream in targetBloodstreams)
{ {
// Begin Offbrand
var beforeInject = new Content.Shared._Offbrand.Chemistry.BeforeInjectOnEventEvent(volumePerBloodstream);
RaiseLocalEvent(targetBloodstream, ref beforeInject);
if (beforeInject.InjectionAmount < 0)
continue;
// End Offbrand
// Take our portion of the adjusted solution for this target // Take our portion of the adjusted solution for this target
var individualInjection = solutionToInject.SplitSolution(volumePerBloodstream); var individualInjection = solutionToInject.SplitSolution(beforeInject.InjectionAmount); // Offbrand
// Inject our portion into the target's bloodstream // Inject our portion into the target's bloodstream
if (_bloodstream.TryAddToChemicals(targetBloodstream.AsNullable(), individualInjection)) if (_bloodstream.TryAddToChemicals(targetBloodstream.AsNullable(), individualInjection))
anySuccess = true; anySuccess = true;

View File

@@ -87,12 +87,14 @@ namespace Content.Server.Construction
{ {
args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n"); args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n");
} }
// Begin Offbrand
else else
{ {
args.PushMarkup(Loc.GetString( args.PushMarkup(Loc.GetString(
"construction-component-to-create-header", target.Header,
("targetName", target.Name)) + "\n"); ("targetName", target.LocalizedName is { } name ? Loc.GetString(name) : target.Name)) + "\n");
} }
// End Offbrand
} }
if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge) if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge)
@@ -165,8 +167,11 @@ namespace Content.Server.Construction
// Initial construction header. // Initial construction header.
new() new()
{ {
Localization = construction.Type == ConstructionType.Structure Localization = construction.Type switch {
? "construction-presenter-to-build" : "construction-presenter-to-craft", ConstructionType.Structure => "construction-presenter-to-build",
ConstructionType.NodeToNode => "construction-presenter-to-node-to-node", // Offbrand
_ => "construction-presenter-to-craft",
},
EntryNumber = step, EntryNumber = step,
} }
}; };
@@ -183,7 +188,7 @@ namespace Content.Server.Construction
return null; return null;
// First steps are handled specially. // First steps are handled specially.
if (step == 1) if (step == 1 && construction.Type != ConstructionType.NodeToNode) // Offbrand
{ {
foreach (var graphStep in edge.Steps) foreach (var graphStep in edge.Steps)
{ {

View File

@@ -67,11 +67,17 @@ namespace Content.Server.Construction
// If we're currently in an edge, we'll let the edge handle or validate the interaction. // If we're currently in an edge, we'll let the edge handle or validate the interaction.
if (GetCurrentEdge(uid, construction) is {} edge) if (GetCurrentEdge(uid, construction) is {} edge)
{ {
var result = HandleEdge(uid, ev, edge, validation, construction); var result = HandleEdge(uid, ev, edge, validation, construction, out _); // Offbrand
// Reset edge index to none if this failed... // Begin Offbrand
if (!validation && result is HandleResult.False && construction.StepIndex == 0) if (result is HandleResult.False && construction.StepIndex == 0)
construction.EdgeIndex = null; {
if (!validation)
construction.EdgeIndex = null;
return HandleNode(uid, ev, node, validation, construction);
}
// End Offbrand
return result; return result;
} }
@@ -101,7 +107,7 @@ namespace Content.Server.Construction
for (var i = 0; i < node.Edges.Count; i++) for (var i = 0; i < node.Edges.Count; i++)
{ {
var edge = node.Edges[i]; var edge = node.Edges[i];
if (HandleEdge(uid, ev, edge, validation, construction) is var result and not HandleResult.False) if (HandleEdge(uid, ev, edge, validation, construction, out var completed) is var result and not HandleResult.False) // Offbrand
{ {
// Only a True result may modify the state. // Only a True result may modify the state.
// In the case of DoAfter, it's only allowed to modify the waiting flag and the current edge index. // In the case of DoAfter, it's only allowed to modify the waiting flag and the current edge index.
@@ -117,7 +123,7 @@ namespace Content.Server.Construction
} }
// If we're not on the same edge as we were before, that means handling that edge changed the node. // If we're not on the same edge as we were before, that means handling that edge changed the node.
if (construction.Node != node.Name) if (completed) // Offbrand
return result; return result;
// If we're still in the same node, that means we entered the edge and it's still not done. // If we're still in the same node, that means we entered the edge and it's still not done.
@@ -139,8 +145,9 @@ namespace Content.Server.Construction
/// <remarks>When <see cref="validation"/> is true, this method will simply return whether the interaction /// <remarks>When <see cref="validation"/> is true, this method will simply return whether the interaction
/// would be handled by the entity or not. It essentially becomes a pure method that modifies nothing.</remarks> /// would be handled by the entity or not. It essentially becomes a pure method that modifies nothing.</remarks>
/// <returns>The result of this interaction with the entity.</returns> /// <returns>The result of this interaction with the entity.</returns>
private HandleResult HandleEdge(EntityUid uid, object ev, ConstructionGraphEdge edge, bool validation, ConstructionComponent? construction = null) private HandleResult HandleEdge(EntityUid uid, object ev, ConstructionGraphEdge edge, bool validation, ConstructionComponent? construction, out bool completed) // Offbrand
{ {
completed = false; // Offbrand
if (!Resolve(uid, ref construction)) if (!Resolve(uid, ref construction))
return HandleResult.False; return HandleResult.False;
@@ -181,6 +188,7 @@ namespace Content.Server.Construction
// We change the node now. // We change the node now.
ChangeNode(uid, user, edge.Target, true, construction); ChangeNode(uid, user, edge.Target, true, construction);
completed = true; // Offbrand
} }
return HandleResult.True; return HandleResult.True;

View File

@@ -184,7 +184,7 @@ namespace Content.Server.Ghost
if (!_minds.TryGetMind(uid, out var mindId, out var mind) || mind.IsVisitingEntity) if (!_minds.TryGetMind(uid, out var mindId, out var mind) || mind.IsVisitingEntity)
return; return;
if (component.MustBeDead && (_mobState.IsAlive(uid) || _mobState.IsCritical(uid))) if (component.MustBeDead && _mobState.IsAlive(uid)) // Offbrand - exit on crit
return; return;
OnGhostAttempt(mindId, component.CanReturn, mind: mind); OnGhostAttempt(mindId, component.CanReturn, mind: mind);

View File

@@ -13,6 +13,7 @@ using Content.Shared.MedicalScanner;
using Content.Shared.Temperature.Components; using Content.Shared.Temperature.Components;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Medical; namespace Content.Server.Medical;
@@ -23,6 +24,7 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly SharedWoundableHealthAnalyzerSystem _woundableHealthAnalyzer = default!; // Offbrand
public override void Initialize() public override void Initialize()
{ {
@@ -59,7 +61,8 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
: 0, : 0,
null, null,
null, null,
null null,
_woundableHealthAnalyzer.TakeSample(entity) // Offbrand
)); ));
} }

View File

@@ -23,6 +23,7 @@ using Content.Shared.PowerCell;
using Content.Shared.Timing; using Content.Shared.Timing;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Player; using Robust.Shared.Player;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Medical; namespace Content.Server.Medical;
@@ -112,10 +113,15 @@ public sealed class DefibrillatorSystem : EntitySystem
if (!_powerCell.HasActivatableCharge(uid, user: user)) if (!_powerCell.HasActivatableCharge(uid, user: user))
return false; return false;
if (!targetCanBeAlive && _mobState.IsAlive(target, mobState)) // Begin Offbrand
if (TryComp<HeartrateComponent>(target, out var heartrate) && heartrate.Running)
return false;
// End Offbrand
if (!targetCanBeAlive && heartrate is null && _mobState.IsAlive(target, mobState)) // Offbrand
return false; return false;
if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState)) if (!targetCanBeAlive && heartrate is null && !component.CanDefibCrit && _mobState.IsCritical(target, mobState)) // Offbrand
return false; return false;
return true; return true;
@@ -176,8 +182,9 @@ public sealed class DefibrillatorSystem : EntitySystem
if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true)) if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
return; return;
var hasDefib = TryComp<HeartDefibrillatableComponent>(target, out var heartDefibrillatable); // Offbrand
if (!TryComp<MobStateComponent>(target, out var mob) || if (!TryComp<MobStateComponent>(target, out var mob) ||
!TryComp<MobThresholdsComponent>(target, out var thresholds)) (!TryComp<MobThresholdsComponent>(target, out var thresholds) && !hasDefib)) // Offbrand
return; return;
_audio.PlayPvs(component.ZapSound, uid); _audio.PlayPvs(component.ZapSound, uid);
@@ -207,11 +214,28 @@ public sealed class DefibrillatorSystem : EntitySystem
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"), _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"),
InGameICChatType.Speak, true); InGameICChatType.Speak, true);
} }
else if (TryComp<UnrevivableComponent>(target, out var unrevivable)) else if (heartDefibrillatable is null && TryComp<UnrevivableComponent>(target, out var unrevivable)) // Offbrand
{ {
_chatManager.TrySendInGameICMessage(uid, Loc.GetString(unrevivable.ReasonMessage), _chatManager.TrySendInGameICMessage(uid, Loc.GetString(unrevivable.ReasonMessage),
InGameICChatType.Speak, true); InGameICChatType.Speak, true);
} }
// Begin offbrand
else if (heartDefibrillatable is not null && _mobState.IsDead(target, mob))
{
_chatManager.TrySendInGameICMessage(uid, Loc.GetString(heartDefibrillatable.TargetIsDead),
InGameICChatType.Speak, true);
}
else if (heartDefibrillatable is not null)
{
var before = new BeforeTargetDefibrillatedEvent(new());
RaiseLocalEvent(target, ref before);
foreach (var message in before.Messages)
{
_chatManager.TrySendInGameICMessage(uid, Loc.GetString(message), InGameICChatType.Speak, true);
}
}
// End Offbrand
else else
{ {
if (_mobState.IsDead(target, mob)) if (_mobState.IsDead(target, mob))

View File

@@ -18,6 +18,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Medical; namespace Content.Server.Medical;
@@ -32,6 +33,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedWoundableHealthAnalyzerSystem _woundableHealthAnalyzer = default!; // Offbrand
public override void Initialize() public override void Initialize()
{ {
@@ -217,7 +219,8 @@ public sealed class HealthAnalyzerSystem : EntitySystem
bloodAmount, bloodAmount,
scanMode, scanMode,
bleeding, bleeding,
unrevivable unrevivable,
_woundableHealthAnalyzer.TakeSample(target) // Offbrand
)); ));
} }
} }

View File

@@ -30,6 +30,7 @@ using Content.Shared.Atmos.Components;
using System.Linq; using System.Linq;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared.Temperature.Components; using Content.Shared.Temperature.Components;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.NPC.Systems; namespace Content.Server.NPC.Systems;
@@ -54,6 +55,7 @@ public sealed class NPCUtilitySystem : EntitySystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!; [Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
[Dependency] private readonly TurretTargetSettingsSystem _turretTargetSettings = default!; [Dependency] private readonly TurretTargetSettingsSystem _turretTargetSettings = default!;
[Dependency] private readonly HealthRankingSystem _healthRanking = default!; // Offbrand
private EntityQuery<PuddleComponent> _puddleQuery; private EntityQuery<PuddleComponent> _puddleQuery;
private EntityQuery<TransformComponent> _xformQuery; private EntityQuery<TransformComponent> _xformQuery;
@@ -304,6 +306,8 @@ public sealed class NPCUtilitySystem : EntitySystem
{ {
if (!TryComp(targetUid, out DamageableComponent? damage)) if (!TryComp(targetUid, out DamageableComponent? damage))
return 0f; return 0f;
if (_healthRanking.RankHealth(targetUid, con.TargetState) is { } ranking) // Offbrand
return ranking; // Offbrand
if (con.TargetState != MobState.Invalid && _thresholdSystem.TryGetPercentageForState(targetUid, con.TargetState, damage.TotalDamage, out var percentage)) if (con.TargetState != MobState.Invalid && _thresholdSystem.TryGetPercentageForState(targetUid, con.TargetState, damage.TotalDamage, out var percentage))
return Math.Clamp((float)(1 - percentage), 0f, 1f); return Math.Clamp((float)(1 - percentage), 0f, 1f);
if (_thresholdSystem.TryGetIncapPercentage(targetUid, damage.TotalDamage, out var incapPercentage)) if (_thresholdSystem.TryGetIncapPercentage(targetUid, damage.TotalDamage, out var incapPercentage))
@@ -335,11 +339,11 @@ public sealed class NPCUtilitySystem : EntitySystem
} }
case TargetIsAliveCon: case TargetIsAliveCon:
{ {
return _mobState.IsAlive(targetUid) ? 1f : 0f; return _mobState.IsAlive(targetUid) && !_healthRanking.IsCritical(targetUid) ? 1f : 0f; // Offbrand
} }
case TargetIsCritCon: case TargetIsCritCon:
{ {
return _mobState.IsCritical(targetUid) ? 1f : 0f; return _healthRanking.IsCritical(targetUid) ? 1f : 0f; // Offbrand
} }
case TargetIsDeadCon: case TargetIsDeadCon:
{ {

View File

@@ -12,6 +12,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
{ {
[Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly RadioSystem _radio = default!; [Dependency] private readonly RadioSystem _radio = default!;
[Dependency] private readonly Content.Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!; // Offbrand
public override void Initialize() public override void Initialize()
{ {
@@ -45,6 +46,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args) private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
{ {
if (args.Channel != null if (args.Channel != null
&& !_statusEffects.HasEffectComp<Content.Shared._Offbrand.StatusEffects.CannotUseHeadsetStatusEffectComponent>(uid) // Offbrand
&& TryComp(component.Headset, out EncryptionKeyHolderComponent? keys) && TryComp(component.Headset, out EncryptionKeyHolderComponent? keys)
&& keys.Channels.Contains(args.Channel.ID)) && keys.Channels.Contains(args.Channel.ID))
{ {

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Utility;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Revenant.EntitySystems; namespace Content.Server.Revenant.EntitySystems;
@@ -46,6 +47,7 @@ public sealed partial class RevenantSystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly HealthRankingSystem _healthRanking = default!; // Offbrand
private static readonly ProtoId<TagPrototype> WindowTag = "Window"; private static readonly ProtoId<TagPrototype> WindowTag = "Window";
@@ -143,7 +145,7 @@ public sealed partial class RevenantSystem
return; return;
} }
if (TryComp<MobStateComponent>(target, out var mobstate) && mobstate.CurrentState == MobState.Alive && !HasComp<SleepingComponent>(target)) if (!_healthRanking.IsCritical(target) && !HasComp<SleepingComponent>(target)) // Offbrand
{ {
_popup.PopupEntity(Loc.GetString("revenant-soul-too-powerful"), target, uid); _popup.PopupEntity(Loc.GetString("revenant-soul-too-powerful"), target, uid);
return; return;
@@ -201,7 +203,7 @@ public sealed partial class RevenantSystem
if (!HasComp<MobStateComponent>(args.Args.Target)) if (!HasComp<MobStateComponent>(args.Args.Target))
return; return;
if (_mobState.IsAlive(args.Args.Target.Value) || _mobState.IsCritical(args.Args.Target.Value)) if (_mobState.IsAlive(args.Args.Target.Value) || _healthRanking.IsCritical(args.Args.Target.Value)) // Offbrand
{ {
_popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid); _popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid);
component.EssenceRegenCap += component.MaxEssenceUpgradeAmount; component.EssenceRegenCap += component.MaxEssenceUpgradeAmount;
@@ -209,11 +211,7 @@ public sealed partial class RevenantSystem
//KILL THEMMMM //KILL THEMMMM
if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage)) _damage.ChangeDamage(args.Args.Target.Value, component.HarvestDamage, true, origin: uid);
return;
DamageSpecifier dspec = new();
dspec.DamageDict.Add("Cold", damage.Value);
_damage.ChangeDamage(args.Args.Target.Value, dspec, true, origin: uid);
args.Handled = true; args.Handled = true;
} }

View File

@@ -43,6 +43,7 @@ using Robust.Shared.Prototypes;
using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Prototypes;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Temperature.Components; using Content.Shared.Temperature.Components;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Zombies; namespace Content.Server.Zombies;
@@ -76,6 +77,8 @@ public sealed partial class ZombieSystem
private static readonly ProtoId<NpcFactionPrototype> ZombieFaction = "Zombie"; private static readonly ProtoId<NpcFactionPrototype> ZombieFaction = "Zombie";
private static readonly string MindRoleZombie = "MindRoleZombie"; private static readonly string MindRoleZombie = "MindRoleZombie";
private static readonly List<ProtoId<AntagPrototype>> BannableZombiePrototypes = ["Zombie"]; private static readonly List<ProtoId<AntagPrototype>> BannableZombiePrototypes = ["Zombie"];
private static readonly EntProtoId AddOnWoundableZombified = "AddOnWoundableZombified"; // Offbrand
private static readonly EntProtoId AddOnAnyZombified = "AddOnAnyZombified"; // Offbrand
/// <summary> /// <summary>
/// Handles an entity turning into a zombie when they die or go into crit /// Handles an entity turning into a zombie when they die or go into crit
@@ -143,6 +146,38 @@ public sealed partial class ZombieSystem
RemComp<ComplexInteractionComponent>(target); RemComp<ComplexInteractionComponent>(target);
RemComp<SentienceTargetComponent>(target); RemComp<SentienceTargetComponent>(target);
// Begin Offbrand
if (RemComp<WoundableComponent>(target))
{
RemComp<HeartrateComponent>(target);
RemComp<HeartDefibrillatableComponent>(target);
RemComp<HeartStopOnHighStrainComponent>(target);
RemComp<PainComponent>(target);
RemComp<PainMetabolicRateComponent>(target);
RemComp<HeartrateAlertsComponent>(target);
RemComp<ShockThresholdsComponent>(target);
RemComp<ShockAlertsComponent>(target);
RemComp<BrainDamageComponent>(target);
RemComp<BrainDamageOxygenationComponent>(target);
RemComp<BrainDamageThresholdsComponent>(target);
RemComp<BrainDamageOnDamageComponent>(target);
RemComp<HeartDamageOnDamageComponent>(target);
RemComp<MaximumDamageComponent>(target);
RemComp<CprTargetComponent>(target);
RemComp<Content.Server.Construction.Components.ConstructionComponent>(target);
RemComp<CryostasisFactorComponent>(target);
RemComp<UniqueWoundOnDamageComponent>(target);
RemComp<IntrinsicPainComponent>(target);
RemComp<LungDamageComponent>(target);
RemComp<LungDamageOnInhaledAirTemperatureComponent>(target);
RemComp<LungDamageAlertsComponent>(target);
var entProto = _protoManager.Index(AddOnWoundableZombified);
EntityManager.RemoveComponents(target, entProto.Components);
EntityManager.AddComponents(target, entProto.Components);
}
// End Offbrand
//funny voice //funny voice
var accentType = "zombie"; var accentType = "zombie";
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent)) if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
@@ -225,11 +260,18 @@ public sealed partial class ZombieSystem
//The zombie gets the assigned damage weaknesses and strengths //The zombie gets the assigned damage weaknesses and strengths
_damageable.SetDamageModifierSetId(target, "Zombie"); _damageable.SetDamageModifierSetId(target, "Zombie");
// Begin Offbrand
var allProto = _protoManager.Index(AddOnAnyZombified);
EntityManager.RemoveComponents(target, allProto.Components);
EntityManager.AddComponents(target, allProto.Components);
// End Offbrand
//This makes it so the zombie doesn't take bloodloss damage. //This makes it so the zombie doesn't take bloodloss damage.
//NOTE: they are supposed to bleed, just not take damage //NOTE: they are supposed to bleed, just not take damage
_bloodstream.SetBloodLossThreshold(target, 0f); _bloodstream.SetBloodLossThreshold(target, 0f);
//Give them zombie blood //Give them zombie blood
_bloodstream.ChangeBloodReagent(target, zombiecomp.NewBloodReagent); _bloodstream.ChangeBloodReagent(target, zombiecomp.NewBloodReagent);
_bloodstream.FlushChemicals(target, null, 100); // Offbrand
//This is specifically here to combat insuls, because frying zombies on grilles is funny as shit. //This is specifically here to combat insuls, because frying zombies on grilles is funny as shit.
_inventory.TryUnequip(target, "gloves", true, true); _inventory.TryUnequip(target, "gloves", true, true);
@@ -253,6 +295,11 @@ public sealed partial class ZombieSystem
_faction.ClearFactions(target, dirty: false); _faction.ClearFactions(target, dirty: false);
_faction.AddFaction(target, ZombieFaction); _faction.AddFaction(target, ZombieFaction);
// Begin Offbrand
var rejuv = new Content.Shared.Rejuvenate.RejuvenateEvent();
RaiseLocalEvent(target, rejuv);
// End Offbrand
//gives it the funny "Zombie ___" name. //gives it the funny "Zombie ___" name.
_nameMod.RefreshNameModifiers(target); _nameMod.RefreshNameModifiers(target);

View File

@@ -26,6 +26,7 @@ using Content.Shared.Zombies;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Zombies namespace Content.Server.Zombies
{ {
@@ -43,6 +44,7 @@ namespace Content.Server.Zombies
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedRoleSystem _role = default!; [Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly Content.Shared._Offbrand.Wounds.BrainDamageSystem _brainDamage = default!; // Offbrand
public readonly ProtoId<NpcFactionPrototype> Faction = "Zombie"; public readonly ProtoId<NpcFactionPrototype> Faction = "Zombie";
@@ -138,6 +140,7 @@ namespace Content.Server.Zombies
: 1f; : 1f;
_damageable.ChangeDamage((uid, damage), comp.Damage * multiplier, true, false); _damageable.ChangeDamage((uid, damage), comp.Damage * multiplier, true, false);
_brainDamage.TryChangeBrainDamage(uid, multiplier / 2f); // Offbrand
} }
// Heal the zombified // Heal the zombified
@@ -256,6 +259,14 @@ namespace Content.Server.Zombies
args.Handled = true; args.Handled = true;
continue; continue;
} }
else if (!HasComp<WoundableComponent>(uid)) // Offbrand
{
if (!HasComp<ZombieImmuneComponent>(uid) && !cannotSpread && _random.Prob(GetZombieInfectionChance(uid, entity.Comp)))
{
EnsureComp<PendingZombieComponent>(uid);
EnsureComp<ZombifyOnDeathComponent>(uid);
}
}
if (_mobState.IsAlive(uid, mobState)) if (_mobState.IsAlive(uid, mobState))
{ {

View File

@@ -0,0 +1,52 @@
using Content.Server.Body.Components;
using Content.Shared._Offbrand.EntityEffects;
using Content.Shared.Body.Organ;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.EntityConditions;
using Content.Shared.FixedPoint;
namespace Content.Server._Offbrand.EntityEffects;
public sealed class MetaboliteThresholdEntityConditionSystem : EntityConditionSystem<MetabolizerComponent, MetaboliteThresholdCondition>
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
private Solution? GetSolution(Entity<MetabolizerComponent, OrganComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp2, false))
return null;
if (ent.Comp1.SolutionOnBody)
{
if (ent.Comp2.Body is { } body && _solutionContainer.TryGetSolution(body, ent.Comp1.SolutionName, out _, out var solution))
return solution;
return null;
}
else
{
if (_solutionContainer.TryGetSolution(ent.Owner, ent.Comp1.SolutionName, out _, out var solution))
return solution;
}
return null;
}
protected override void Condition(Entity<MetabolizerComponent> ent, ref EntityConditionEvent<MetaboliteThresholdCondition> args)
{
var reagent = args.Condition.Reagent;
var metabolites = ent.Comp.Metabolites;
var quant = FixedPoint2.Zero;
metabolites.TryGetValue(reagent, out quant);
if (args.Condition.IncludeBloodstream && GetSolution((ent, ent.Comp, null)) is { } solution)
{
quant += solution.GetTotalPrototypeQuantity(reagent);
}
args.Result = quant >= args.Condition.Min && quant <= args.Condition.Max;
}
}

View File

@@ -0,0 +1,15 @@
using Content.Server.Zombies;
using Content.Shared._Offbrand.EntityEffects;
using Content.Shared.EntityEffects;
namespace Content.Server._Offbrand.EntityEffects;
public sealed class ZombifySystem : EntityEffectSystem<MetaDataComponent, Zombify>
{
[Dependency] private readonly ZombieSystem _zombie = default!;
protected override void Effect(Entity<MetaDataComponent> ent, ref EntityEffectEvent<Zombify> args)
{
_zombie.ZombifyEntity(ent);
}
}

View File

@@ -0,0 +1,30 @@
using Content.Server.EUI;
using Content.Shared._Offbrand.MMI;
using Content.Shared.DoAfter;
using Content.Shared.Eui;
namespace Content.Server._Offbrand.MMI;
public sealed class MMIExtractorEui : BaseEui
{
private readonly MMIExtractorSystem _mmiExtractor;
private readonly DoAfterId _doAfterId;
public MMIExtractorEui(MMIExtractorSystem mmiExtractor, DoAfterId doAfterId)
{
_mmiExtractor = mmiExtractor;
_doAfterId = doAfterId;
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
if (msg is not MMIExtractorMessage choice || !choice.Accepted)
_mmiExtractor.Decline(_doAfterId);
else
_mmiExtractor.Accept(_doAfterId);
Close();
}
}

View File

@@ -0,0 +1,171 @@
using Content.Server.EUI;
using Content.Shared._Offbrand.MMI;
using Content.Shared._Offbrand.Wounds;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chat;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Mind;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Player;
namespace Content.Server._Offbrand.MMI;
public sealed class MMIExtractorSystem : EntitySystem
{
[Dependency] private readonly BrainDamageSystem _brainDamage = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly EuiManager _eui = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly ItemSlotsSystem _slots = default!;
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly SharedChatSystem _chat = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MMIExtractorComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<BrainComponent, MMIExtractorDoAfterEvent>(OnDoAfter);
}
private void OnAfterInteract(Entity<MMIExtractorComponent> ent, ref AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target == null)
return;
if (TryExtract(ent, args.Target.Value, args.User))
args.Handled = true;
}
private bool TryExtract(Entity<MMIExtractorComponent> ent, EntityUid target, EntityUid user)
{
if (!_whitelist.CheckBoth(target, ent.Comp.Blacklist, ent.Comp.Whitelist))
return false;
if (!_mind.TryGetMind(target, out _, out var mind) || !_player.TryGetSessionById(mind.UserId, out var playerSession))
{
_chat.TrySendInGameICMessage(ent,
Loc.GetString(ent.Comp.NoMind),
InGameICChatType.Speak,
true);
return true;
}
if (!_body.TryGetBodyOrganEntityComps<BrainComponent>(target, out var organs))
{
_chat.TrySendInGameICMessage(ent,
Loc.GetString(ent.Comp.Brainless),
InGameICChatType.Speak,
true);
return true;
}
if (organs.Count != 1)
{
_chat.TrySendInGameICMessage(ent,
Loc.GetString(ent.Comp.TooManyBrains),
InGameICChatType.Speak,
true);
return true;
}
var brain = organs[0];
_chat.TrySendInGameICMessage(ent,
Loc.GetString(ent.Comp.Asking),
InGameICChatType.Speak,
true);
var args =
new DoAfterArgs(EntityManager, user, ent.Comp.Delay, new MMIExtractorDoAfterEvent(), brain, target: target, used: ent)
{
NeedHand = true,
BreakOnMove = true,
BreakOnWeightlessMove = false,
};
if (_doAfter.TryStartDoAfter(args, out var id))
_eui.OpenEui(new MMIExtractorEui(this, id.Value), playerSession);
return true;
}
public void Decline(DoAfterId id)
{
_doAfter.Cancel(id);
if (!TryComp<DoAfterComponent>(id.Uid, out var doAfters))
return;
var dict = doAfters.DoAfters; // i love access workarounds
if (!dict.TryGetValue(id.Index, out var doAfter))
return;
if (doAfter.Args.Used is not { } mmi || !TryComp<MMIExtractorComponent>(mmi, out var mmiComp))
return;
_chat.TrySendInGameICMessage(mmi,
Loc.GetString(mmiComp.Denied),
InGameICChatType.Speak,
true);
}
public void Accept(DoAfterId id)
{
if (!TryComp<DoAfterComponent>(id.Uid, out var doAfters))
return;
var dict = doAfters.DoAfters; // i love access workarounds
if (!dict.TryGetValue(id.Index, out var doAfter))
return;
if (doAfter.Args.Used is not { } mmi || !TryComp<MMIExtractorComponent>(mmi, out var mmiComp))
return;
if (doAfter.Args.Event is not MMIExtractorDoAfterEvent evt)
return;
evt.Accepted = true;
_chat.TrySendInGameICMessage(mmi,
Loc.GetString(mmiComp.Accepted),
InGameICChatType.Speak,
true);
}
private void OnDoAfter(Entity<BrainComponent> ent, ref MMIExtractorDoAfterEvent evt)
{
if (evt.Handled || evt.Cancelled)
return;
if (evt.Args.Used is not { } mmi || !TryComp<MMIExtractorComponent>(mmi, out var mmiComp))
return;
if (!TryComp<MMIComponent>(mmi, out var insertionComp))
return;
if (!evt.Accepted)
{
_chat.TrySendInGameICMessage(mmi,
Loc.GetString(mmiComp.NoResponse),
InGameICChatType.Speak,
true);
return;
}
if (!_slots.TryInsert(mmi, insertionComp.BrainSlotId, ent, null))
return;
if (evt.Args.Target is { } target)
_brainDamage.KillBrain(target);
}
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.Construction;
using Content.Server.Construction;
using Robust.Shared.Prototypes;
namespace Content.Server._Offbrand.Surgery;
/// <summary>
/// Sets the current node of the construction graph, and clears the pathfinding target if the repeat conditions are not met
/// </summary>
[DataDefinition]
public sealed partial class SetNode : IGraphAction
{
[DataField(required: true)]
public string Node;
[DataField]
public List<IGraphCondition> RepeatConditions = new();
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
{
var construction = entityManager.System<ConstructionSystem>();
if (!construction.CheckConditions(uid, RepeatConditions) || RepeatConditions.Count == 0)
{
construction.SetPathfindingTarget(uid, null);
}
construction.ChangeNode(uid, userUid, Node);
construction.ResetEdge(uid);
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.Construction;
using Content.Shared._Offbrand.Surgery;
using Robust.Shared.Prototypes;
namespace Content.Server._Offbrand.Surgery;
public sealed class SurgeryGuideTargetSystem : SharedSurgeryGuideTargetSystem
{
[Dependency] private readonly ConstructionSystem _construction = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
protected override void OnStartSurgery(Entity<SurgeryGuideTargetComponent> ent, ref SurgeryGuideStartSurgeryMessage args)
{
base.OnStartSurgery(ent, ref args);
if (!_prototype.Resolve(args.Prototype, out var construction))
return;
_construction.SetPathfindingTarget(ent, construction.TargetNode);
}
protected override void OnStartCleanup(Entity<SurgeryGuideTargetComponent> ent, ref SurgeryGuideStartCleanupMessage args)
{
base.OnStartCleanup(ent, ref args);
_construction.SetPathfindingTarget(ent, "Base");
}
}

View File

@@ -0,0 +1,22 @@
using Robust.Shared.Prototypes;
namespace Content.Server._Offbrand.VariationPass;
/// <summary>
/// A bodge component to spawn the given entities near the lathe of the given prototype in lieu of mapping effort
/// </summary>
[RegisterComponent]
public sealed partial class SupplyNearLatheVariationPassComponent : Component
{
/// <summary>
/// The prototype of the lathe to look for
/// </summary>
[DataField(required: true)]
public EntProtoId LathePrototype;
/// <summary>
/// The entity to spawn on said lathe
/// </summary>
[DataField(required: true)]
public EntProtoId EntityToSpawn;
}

View File

@@ -0,0 +1,30 @@
using Content.Server.GameTicking.Rules.VariationPass;
using Content.Server.GameTicking.Rules;
using Content.Shared.Examine;
using Content.Shared.Lathe;
using Robust.Shared.Prototypes;
namespace Content.Server._Offbrand.VariationPass;
/// <inheritdoc cref="SupplyNearLatheVariationPassComponent"/>
public sealed class SupplyNearLatheVariationPassSystem : VariationPassSystem<SupplyNearLatheVariationPassComponent>
{
private EntityUid? FindLatheOnStation(EntProtoId proto, EntityUid station)
{
var query = AllEntityQuery<LatheComponent>();
while (query.MoveNext(out var uid, out _))
{
if (MetaData(uid).EntityPrototype?.ID is { } existingProto && existingProto == proto)
return uid;
}
return null;
}
protected override void ApplyVariation(Entity<SupplyNearLatheVariationPassComponent> ent, ref StationVariationPassEvent args)
{
if (FindLatheOnStation(ent.Comp.LathePrototype, args.Station) is not { } lathe)
return;
SpawnNextToOrDrop(ent.Comp.EntityToSpawn, lathe);
}
}

View File

@@ -0,0 +1,74 @@
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Shared._Offbrand.Wounds;
namespace Content.Server._Offbrand.Wounds;
public sealed class BrainGaspThresholdsSystem : EntitySystem
{
[Dependency] private readonly ChatSystem _chat = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BrainGaspThresholdsComponent, AfterBrainDamageChanged>(OnAfterBrainDamageChanged);
SubscribeLocalEvent<BrainGaspThresholdsComponent, AfterBrainOxygenChanged>(OnAfterBrainOxygenChanged);
}
private void OnAfterBrainDamageChanged(Entity<BrainGaspThresholdsComponent> ent, ref AfterBrainDamageChanged args)
{
var brain = Comp<BrainDamageComponent>(ent);
var message = ent.Comp.MessageThresholds.HighestMatch(brain.Damage);
if (message == ent.Comp.CurrentMessage)
return;
var previousMessage = ent.Comp.CurrentMessage;
ent.Comp.CurrentMessage = message;
Dirty(ent);
if (previousMessage is { } previous)
{
var previousKey = ent.Comp.MessageThresholds.FirstOrDefault(x => x.Value == previous).Key;
var currentKey = ent.Comp.MessageThresholds.FirstOrDefault(x => x.Value == message).Key;
if (previousKey >= currentKey)
{
return;
}
}
if (message is { } msg)
_chat.TryEmoteWithChat(ent.Owner, msg, ignoreActionBlocker: true);
}
private void OnAfterBrainOxygenChanged(Entity<BrainGaspThresholdsComponent> ent, ref AfterBrainOxygenChanged args)
{
var brain = Comp<BrainDamageComponent>(ent);
var message = ent.Comp.OxygenThresholds.LowestMatch(brain.Oxygen);
if (message == ent.Comp.CurrentOxygen)
return;
var previousMessage = ent.Comp.CurrentOxygen;
ent.Comp.CurrentOxygen = message;
Dirty(ent);
if (previousMessage is { } previous)
{
var previousKey = ent.Comp.OxygenThresholds.FirstOrDefault(x => x.Value == previous).Key;
var currentKey = ent.Comp.OxygenThresholds.FirstOrDefault(x => x.Value == message).Key;
if (previousKey <= currentKey)
{
return;
}
}
if (message is { } msg)
_chat.TryEmoteWithChat(ent.Owner, msg, ignoreActionBlocker: true);
}
}

View File

@@ -0,0 +1,46 @@
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Shared._Offbrand.Wounds;
namespace Content.Server._Offbrand.Wounds;
public sealed class ShockGaspThresholdsSystem : EntitySystem
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly PainSystem _pain = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ShockGaspThresholdsComponent, AfterShockChangeEvent>(OnAfterShockChange);
}
private void OnAfterShockChange(Entity<ShockGaspThresholdsComponent> ent, ref AfterShockChangeEvent args)
{
var shock = _pain.GetShock(ent.Owner);
var message = ent.Comp.MessageThresholds.HighestMatch(shock);
if (message == ent.Comp.CurrentMessage)
return;
var previousMessage = ent.Comp.CurrentMessage;
ent.Comp.CurrentMessage = message;
Dirty(ent);
if (previousMessage is { } previous)
{
var previousKey = ent.Comp.MessageThresholds.FirstOrDefault(x => x.Value == previous).Key;
var currentKey = ent.Comp.MessageThresholds.FirstOrDefault(x => x.Value == message).Key;
if (previousKey >= currentKey)
{
return;
}
}
if (message is { } msg)
_chat.TryEmoteWithChat(ent.Owner, msg, ignoreActionBlocker: true);
}
}

View File

@@ -0,0 +1,93 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Shared._Offbrand.Wounds;
using Content.Shared.Atmos;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server._Offbrand.Wounds;
public sealed class WoundableHealthAnalyzerSystem : SharedWoundableHealthAnalyzerSystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
public override Dictionary<ProtoId<ReagentPrototype>, (FixedPoint2 InBloodstream, FixedPoint2 Metabolites)>? SampleReagents(EntityUid uid, out bool hasNonMedical)
{
hasNonMedical = false;
if (!TryComp<BloodstreamComponent>(uid, out var bloodstream))
return null;
if (!_solutionContainer.ResolveSolution(uid, bloodstream.ChemicalSolutionName, ref bloodstream.ChemicalSolution))
return null;
var ret = new Dictionary<ProtoId<ReagentPrototype>, (FixedPoint2 InBloodstream, FixedPoint2 Metabolites)>();
foreach (var (reagentId, quantity) in bloodstream.ChemicalSolution.Value.Comp.Solution.Contents)
{
ProtoId<ReagentPrototype> reagent = reagentId.Prototype;
if (_prototype.Index(reagent).Group != MedicineGroup)
{
hasNonMedical = true;
continue;
}
if (!ret.ContainsKey(reagent))
ret[reagent] = (0, 0);
ret[reagent] = (ret[reagent].InBloodstream + quantity, ret[reagent].Metabolites);
}
foreach (var metabolizer in _body.GetBodyOrganEntityComps<MetabolizerComponent>(uid))
{
if (metabolizer.Comp1.SolutionName != bloodstream.ChemicalSolutionName)
continue;
foreach (var (reagent, quantity) in metabolizer.Comp1.Metabolites)
{
if (_prototype.Index(reagent).Group != MedicineGroup)
{
hasNonMedical = true;
continue;
}
if (!ret.ContainsKey(reagent))
ret[reagent] = (0, 0);
ret[reagent] = (ret[reagent].InBloodstream, ret[reagent].Metabolites + quantity);
}
}
foreach (var lung in _body.GetBodyOrganEntityComps<LungComponent>(uid))
{
foreach (var gasId in Enum.GetValues<Gas>())
{
var idx = (int) gasId;
var moles = lung.Comp1.Air[idx];
if (moles <= 0)
continue;
if (_atmosphere.GasReagents[idx] is not { } reagent)
continue;
var amount = FixedPoint2.New(moles * Atmospherics.BreathMolesToReagentMultiplier);
if (amount <= 0)
continue;
if (!ret.ContainsKey(reagent))
ret[reagent] = (0, 0);
ret[reagent] = (ret[reagent].InBloodstream + amount, ret[reagent].Metabolites);
}
}
return ret;
}
}

View File

@@ -85,14 +85,14 @@ public sealed partial class BloodstreamComponent : Component
/// The default values are defined per mob/species in YML. /// The default values are defined per mob/species in YML.
/// </summary> /// </summary>
[DataField(required: true), AutoNetworkedField] [DataField(required: true), AutoNetworkedField]
public DamageSpecifier BloodlossDamage = new(); public DamageSpecifier? BloodlossDamage = new(); // Offbrand: we don't need this
/// <summary> /// <summary>
/// The base bloodloss damage to be healed if above <see cref="BloodlossThreshold"/> /// The base bloodloss damage to be healed if above <see cref="BloodlossThreshold"/>
/// The default values are defined per mob/species in YML. /// The default values are defined per mob/species in YML.
/// </summary> /// </summary>
[DataField(required: true), AutoNetworkedField] [DataField(required: true), AutoNetworkedField]
public DamageSpecifier BloodlossHealDamage = new(); public DamageSpecifier? BloodlossHealDamage = new(); // Offbrand: we don't need this
// TODO shouldn't be hardcoded, should just use some organ simulation like bone marrow or smth. // TODO shouldn't be hardcoded, should just use some organ simulation like bone marrow or smth.
/// <summary> /// <summary>
@@ -115,7 +115,7 @@ public sealed partial class BloodstreamComponent : Component
/// For example, piercing damage is increased while poison damage is nullified entirely. /// For example, piercing damage is increased while poison damage is nullified entirely.
/// </remarks> /// </remarks>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public ProtoId<DamageModifierSetPrototype> DamageBleedModifiers = "BloodlossHuman"; public ProtoId<DamageModifierSetPrototype>? DamageBleedModifiers = "BloodlossHuman"; // Offbrand: we don't want this
/// <summary> /// <summary>
/// The sound to be played when a weapon instantly deals blood loss damage. /// The sound to be played when a weapon instantly deals blood loss damage.

View File

@@ -78,11 +78,14 @@ public abstract class SharedBloodstreamSystem : EntitySystem
TryModifyBloodLevel((uid, bloodstream), bloodstream.BloodRefreshAmount); TryModifyBloodLevel((uid, bloodstream), bloodstream.BloodRefreshAmount);
} }
// Begin Offbrand
var bleedLevel = EffectiveBleedLevel((uid, bloodstream));
// Removes blood from the bloodstream based on bleed amount (bleed rate) // Removes blood from the bloodstream based on bleed amount (bleed rate)
// as well as stop their bleeding to a certain extent. // as well as stop their bleeding to a certain extent.
if (bloodstream.BleedAmount > 0) if (bleedLevel > 0)
{ {
var ev = new BleedModifierEvent(bloodstream.BleedAmount, bloodstream.BleedReductionAmount); var ev = new BleedModifierEvent(bleedLevel, bloodstream.BleedReductionAmount);
RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(uid, ref ev);
// Blood is removed from the bloodstream at a 1-1 rate with the bleed amount // Blood is removed from the bloodstream at a 1-1 rate with the bleed amount
@@ -92,9 +95,18 @@ public abstract class SharedBloodstreamSystem : EntitySystem
TryModifyBleedAmount((uid, bloodstream), -ev.BleedReductionAmount); TryModifyBleedAmount((uid, bloodstream), -ev.BleedReductionAmount);
} }
if (bleedLevel == 0)
_alertsSystem.ClearAlert(uid, bloodstream.BleedingAlert);
else
{
var severity = (short)Math.Clamp(Math.Round(bleedLevel, MidpointRounding.ToZero), 0, 10);
_alertsSystem.ShowAlert(uid, bloodstream.BleedingAlert, severity);
}
// End Offbrand
// deal bloodloss damage if their blood level is below a threshold. // deal bloodloss damage if their blood level is below a threshold.
var bloodPercentage = GetBloodLevelPercentage((uid, bloodstream)); var bloodPercentage = GetBloodLevelPercentage((uid, bloodstream));
if (bloodPercentage < bloodstream.BloodlossThreshold && !_mobStateSystem.IsDead(uid)) if (bloodPercentage < bloodstream.BloodlossThreshold && !_mobStateSystem.IsDead(uid) && bloodstream.BloodlossDamage is not null) // Offbrand
{ {
// bloodloss damage is based on the base value, and modified by how low your blood level is. // bloodloss damage is based on the base value, and modified by how low your blood level is.
var amt = bloodstream.BloodlossDamage / (0.1f + bloodPercentage); var amt = bloodstream.BloodlossDamage / (0.1f + bloodPercentage);
@@ -106,7 +118,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
// Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out // Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out
_status.TrySetStatusEffectDuration(uid, Bloodloss); _status.TrySetStatusEffectDuration(uid, Bloodloss);
} }
else if (!_mobStateSystem.IsDead(uid)) else if (!_mobStateSystem.IsDead(uid) && bloodstream.BloodlossHealDamage is not null) // Offbrand
{ {
// If they're healthy, we'll try and heal some bloodloss instead. // If they're healthy, we'll try and heal some bloodloss instead.
_damageableSystem.TryChangeDamage( _damageableSystem.TryChangeDamage(
@@ -243,26 +255,28 @@ public abstract class SharedBloodstreamSystem : EntitySystem
/// </summary> /// </summary>
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args) private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
{ {
var bleedLevel = EffectiveBleedLevel(ent); // Offbrand
// Shows massively bleeding at 0.75x the max bleed rate. // Shows massively bleeding at 0.75x the max bleed rate.
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.75f) if (bleedLevel > ent.Comp.MaxBleedAmount * 0.75f) // Offbrand
{ {
args.Message.PushNewline(); args.Message.PushNewline();
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-massive-bleeding", ("target", ent.Owner))); args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-massive-bleeding", ("target", ent.Owner)));
} }
// Shows bleeding message when bleeding above half the max rate, but less than massively. // Shows bleeding message when bleeding above half the max rate, but less than massively.
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.5f) else if (bleedLevel > ent.Comp.MaxBleedAmount * 0.5f) // Offbrand
{ {
args.Message.PushNewline(); args.Message.PushNewline();
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-strong-bleeding", ("target", ent.Owner))); args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-strong-bleeding", ("target", ent.Owner)));
} }
// Shows bleeding message when bleeding above 0.25x the max rate, but less than half the max. // Shows bleeding message when bleeding above 0.25x the max rate, but less than half the max.
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.25f) else if (bleedLevel > ent.Comp.MaxBleedAmount * 0.25f) // Offbrand
{ {
args.Message.PushNewline(); args.Message.PushNewline();
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner))); args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
} }
// Shows bleeding message when bleeding below 0.25x the max cap // Shows bleeding message when bleeding below 0.25x the max cap
else if (ent.Comp.BleedAmount > 0) else if (bleedLevel > 0) // Offbrand
{ {
args.Message.PushNewline(); args.Message.PushNewline();
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-slight-bleeding", ("target", ent.Owner))); args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-slight-bleeding", ("target", ent.Owner)));
@@ -401,6 +415,19 @@ public abstract class SharedBloodstreamSystem : EntitySystem
return true; return true;
} }
// Begin Offbrand
public float EffectiveBleedLevel(Entity<BloodstreamComponent> ent)
{
var evt = new Content.Shared._Offbrand.Wounds.GetBleedLevelEvent(ent.Comp.BleedAmount);
RaiseLocalEvent(ent, ref evt);
var modifiers = new Content.Shared._Offbrand.Wounds.ModifyBleedLevelEvent(evt.BleedLevel);
RaiseLocalEvent(ent, ref modifiers);
return modifiers.BleedLevel;
}
// End Offbrand
/// <summary> /// <summary>
/// Tries to make an entity bleed more or less. /// Tries to make an entity bleed more or less.
/// </summary> /// </summary>
@@ -414,13 +441,17 @@ public abstract class SharedBloodstreamSystem : EntitySystem
DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BleedAmount)); DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BleedAmount));
if (ent.Comp.BleedAmount == 0) // Begin Offbrand
var bleedLevel = EffectiveBleedLevel((ent, ent.Comp));
if (bleedLevel == 0)
_alertsSystem.ClearAlert(ent.Owner, ent.Comp.BleedingAlert); _alertsSystem.ClearAlert(ent.Owner, ent.Comp.BleedingAlert);
else else
{ {
var severity = (short)Math.Clamp(Math.Round(ent.Comp.BleedAmount, MidpointRounding.ToZero), 0, 10); var severity = (short)Math.Clamp(Math.Round(bleedLevel, MidpointRounding.ToZero), 0, 10);
_alertsSystem.ShowAlert(ent.Owner, ent.Comp.BleedingAlert, severity); _alertsSystem.ShowAlert(ent.Owner, ent.Comp.BleedingAlert, severity);
} }
// End Offbrand
return true; return true;
} }

View File

@@ -16,6 +16,7 @@ public sealed partial class StrapComponent : Component
/// The entities that are currently buckled to this strap. /// The entities that are currently buckled to this strap.
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
[Access(typeof(SharedBuckleSystem), Other = AccessPermissions.ReadExecute)] // Offbrand
public HashSet<EntityUid> BuckledEntities = new(); public HashSet<EntityUid> BuckledEntities = new();
/// <summary> /// <summary>

View File

@@ -265,7 +265,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
} }
protected void UpdateAppearance(Entity<AppearanceComponent?> container, Entity<SolutionComponent, ContainedSolutionComponent> soln) public void UpdateAppearance(Entity<AppearanceComponent?> container, Entity<SolutionComponent, ContainedSolutionComponent> soln) // Offbrand
{ {
var (uid, appearanceComponent) = container; var (uid, appearanceComponent) = container;
if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, logMissing: false)) if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, logMissing: false))

View File

@@ -272,6 +272,12 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("metabolismRate")] [DataField("metabolismRate")]
public FixedPoint2 MetabolismRate = FixedPoint2.New(0.5f); public FixedPoint2 MetabolismRate = FixedPoint2.New(0.5f);
/// <summary>
/// Offbrand: Status effects to apply whilst this reagent is metabolising
/// </summary>
[DataField]
public List<ReagentStatusEffectEntry> StatusEffects = new();
/// <summary> /// <summary>
/// A list of effects to apply when these reagents are metabolized. /// A list of effects to apply when these reagents are metabolized.
/// </summary> /// </summary>
@@ -287,6 +293,30 @@ namespace Content.Shared.Chemistry.Reagent
} }
} }
// Begin Offbrand
[DataDefinition]
public sealed partial class ReagentStatusEffectEntry
{
[DataField]
public Content.Shared.EntityConditions.EntityCondition[]? Conditions;
[DataField]
public EntProtoId StatusEffect;
public string? Describe(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (!prototype.Resolve(StatusEffect, out var effectProtoData))
return null;
return Loc.GetString("reagent-guidebook-status-effect", ("effect", effectProtoData.Name ?? string.Empty),
("conditionCount", Conditions?.Length ?? 0),
("conditions",
Content.Shared.Localizations.ContentLocalizationManager.FormatList(Conditions?.Select(x => x.EntityConditionGuidebookText(prototype)).ToList() ??
new List<string>())));
}
}
// End Offbrand
[Serializable, NetSerializable] [Serializable, NetSerializable]
public struct ReagentEffectsGuideEntry public struct ReagentEffectsGuideEntry
{ {

View File

@@ -31,6 +31,14 @@ namespace Content.Shared.Construction
[DataField("entity", customTypeSerializer: typeof(GraphNodeEntitySerializer))] [DataField("entity", customTypeSerializer: typeof(GraphNodeEntitySerializer))]
public IGraphNodeEntity Entity { get; private set; } = new NullNodeEntity(); public IGraphNodeEntity Entity { get; private set; } = new NullNodeEntity();
// Begin Offbrand
[DataField]
public LocId? LocalizedName;
[DataField]
public LocId Header = "construction-component-to-create-header";
// End Offbrand
/// <summary> /// <summary>
/// Ignore requests to change the entity if the entity's current prototype inherits from specified replacement /// Ignore requests to change the entity if the entity's current prototype inherits from specified replacement
/// </summary> /// </summary>

View File

@@ -98,4 +98,5 @@ public enum ConstructionType
{ {
Structure, Structure,
Item, Item,
NodeToNode, // Offbrand
} }

View File

@@ -46,6 +46,13 @@ namespace Content.Shared.Construction.Steps
return typeof(PartAssemblyConstructionGraphStep); return typeof(PartAssemblyConstructionGraphStep);
} }
// Begin Offbrand
if (node.Has("whitelist") || node.Has("blacklist"))
{
return typeof(Content.Shared._Offbrand.Surgery.WhitelistConstructionGraphStep);
}
// End Offbrand
return null; return null;
} }

View File

@@ -78,6 +78,24 @@ namespace Content.Shared.Damage
return false; return false;
} }
// Begin Offbrand
/// <summary>
/// Returns true if the specifier contains any negative damage values.
/// Differs from <see cref="Empty"/> as a damage specifier might contain entries with zeroes.
/// This also returns false if the specifier only contains negative values.
/// </summary>
public bool AnyNegative()
{
foreach (var value in DamageDict.Values)
{
if (value < FixedPoint2.Zero)
return true;
}
return false;
}
// End Offbrand
/// <summary> /// <summary>
/// Whether this damage specifier has any entries. /// Whether this damage specifier has any entries.
/// </summary> /// </summary>

View File

@@ -69,12 +69,13 @@ public sealed partial class DamageableSystem
bool ignoreResistances = false, bool ignoreResistances = false,
bool interruptsDoAfters = true, bool interruptsDoAfters = true,
EntityUid? origin = null, EntityUid? origin = null,
bool ignoreGlobalModifiers = false bool ignoreGlobalModifiers = false,
bool forceRefresh = false // Offbrand
) )
{ {
//! Empty just checks if the DamageSpecifier is _literally_ empty, as in, is internal dictionary of damage types is empty. //! Empty just checks if the DamageSpecifier is _literally_ empty, as in, is internal dictionary of damage types is empty.
// If you deal 0.0 of some damage type, Empty will be false! // If you deal 0.0 of some damage type, Empty will be false!
return !TryChangeDamage(ent, damage, out _, ignoreResistances, interruptsDoAfters, origin, ignoreGlobalModifiers); return !TryChangeDamage(ent, damage, out _, ignoreResistances, interruptsDoAfters, origin, ignoreGlobalModifiers, forceRefresh); // Offbrand
} }
/// <summary> /// <summary>
@@ -95,12 +96,13 @@ public sealed partial class DamageableSystem
bool ignoreResistances = false, bool ignoreResistances = false,
bool interruptsDoAfters = true, bool interruptsDoAfters = true,
EntityUid? origin = null, EntityUid? origin = null,
bool ignoreGlobalModifiers = false bool ignoreGlobalModifiers = false,
bool forceRefresh = false // Offbrand
) )
{ {
//! Empty just checks if the DamageSpecifier is _literally_ empty, as in, is internal dictionary of damage types is empty. //! Empty just checks if the DamageSpecifier is _literally_ empty, as in, is internal dictionary of damage types is empty.
// If you deal 0.0 of some damage type, Empty will be false! // If you deal 0.0 of some damage type, Empty will be false!
newDamage = ChangeDamage(ent, damage, ignoreResistances, interruptsDoAfters, origin, ignoreGlobalModifiers); newDamage = ChangeDamage(ent, damage, ignoreResistances, interruptsDoAfters, origin, ignoreGlobalModifiers, forceRefresh); // Offbrand
return !damage.Empty; return !damage.Empty;
} }
@@ -121,7 +123,8 @@ public sealed partial class DamageableSystem
bool ignoreResistances = false, bool ignoreResistances = false,
bool interruptsDoAfters = true, bool interruptsDoAfters = true,
EntityUid? origin = null, EntityUid? origin = null,
bool ignoreGlobalModifiers = false bool ignoreGlobalModifiers = false,
bool forceRefresh = false // Offbrand
) )
{ {
var damageDone = new DamageSpecifier(); var damageDone = new DamageSpecifier();
@@ -129,7 +132,7 @@ public sealed partial class DamageableSystem
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false)) if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
return damageDone; return damageDone;
if (damage.Empty) if (damage.Empty && !forceRefresh)
return damageDone; return damageDone;
var before = new BeforeDamageChangedEvent(damage, origin); var before = new BeforeDamageChangedEvent(damage, origin);
@@ -153,13 +156,18 @@ public sealed partial class DamageableSystem
RaiseLocalEvent(ent, ev); RaiseLocalEvent(ent, ev);
damage = ev.Damage; damage = ev.Damage;
if (damage.Empty) if (damage.Empty && !forceRefresh) // Offbrand
return damageDone; return damageDone;
} }
if (!ignoreGlobalModifiers) if (!ignoreGlobalModifiers)
damage = ApplyUniversalAllModifiers(damage); damage = ApplyUniversalAllModifiers(damage);
// Begin Offbrand
var beforeCommit = new Content.Shared._Offbrand.Wounds.BeforeDamageCommitEvent(damage, forceRefresh);
RaiseLocalEvent(ent.Owner, ref beforeCommit);
damage = beforeCommit.Damage;
// End Offbrand
damageDone.DamageDict.EnsureCapacity(damage.DamageDict.Count); damageDone.DamageDict.EnsureCapacity(damage.DamageDict.Count);
@@ -179,7 +187,7 @@ public sealed partial class DamageableSystem
} }
if (!damageDone.Empty) if (!damageDone.Empty)
OnEntityDamageChanged((ent, ent.Comp), damageDone, interruptsDoAfters, origin); OnEntityDamageChanged((ent, ent.Comp), damageDone, interruptsDoAfters, origin, forceRefresh); // Offbrand
return damageDone; return damageDone;
} }

View File

@@ -261,16 +261,23 @@ public sealed class DamageChangedEvent : EntityEventArgs
/// </summary> /// </summary>
public readonly EntityUid? Origin; public readonly EntityUid? Origin;
/// <summary>
/// Offbrand - If this damage changed happened as part of a forced refresh
/// </summary>
public readonly bool ForcedRefresh;
public DamageChangedEvent( public DamageChangedEvent(
DamageableComponent damageable, DamageableComponent damageable,
DamageSpecifier? damageDelta, DamageSpecifier? damageDelta,
bool interruptsDoAfters, bool interruptsDoAfters,
EntityUid? origin EntityUid? origin,
bool forcedRefresh // Offbrand
) )
{ {
Damageable = damageable; Damageable = damageable;
DamageDelta = damageDelta; DamageDelta = damageDelta;
Origin = origin; Origin = origin;
ForcedRefresh = forcedRefresh; // Offbrand
if (DamageDelta is null) if (DamageDelta is null)
return; return;

View File

@@ -47,7 +47,8 @@ public sealed partial class DamageableSystem : EntitySystem
Entity<DamageableComponent> ent, Entity<DamageableComponent> ent,
DamageSpecifier? damageDelta = null, DamageSpecifier? damageDelta = null,
bool interruptsDoAfters = true, bool interruptsDoAfters = true,
EntityUid? origin = null EntityUid? origin = null,
bool forceRefresh = false // Offbrand
) )
{ {
ent.Comp.Damage.GetDamagePerGroup(_prototypeManager, ent.Comp.DamagePerGroup); ent.Comp.Damage.GetDamagePerGroup(_prototypeManager, ent.Comp.DamagePerGroup);
@@ -66,7 +67,7 @@ public sealed partial class DamageableSystem : EntitySystem
// TODO DAMAGE // TODO DAMAGE
// byref struct event. // byref struct event.
RaiseLocalEvent(ent, new DamageChangedEvent(ent.Comp, damageDelta, interruptsDoAfters, origin)); RaiseLocalEvent(ent, new DamageChangedEvent(ent.Comp, damageDelta, interruptsDoAfters, origin, forceRefresh)); // Offbrand
} }
private void DamageableGetState(Entity<DamageableComponent> ent, ref ComponentGetState args) private void DamageableGetState(Entity<DamageableComponent> ent, ref ComponentGetState args)

View File

@@ -46,6 +46,13 @@ namespace Content.Shared.FixedPoint
return new(value * ShiftConstant); return new(value * ShiftConstant);
} }
// Begin Offbrand
public static FixedPoint2 FromRaw(int value)
{
return new(value);
}
// End Offbrand
public static FixedPoint2 FromCents(int value) => new(value); public static FixedPoint2 FromCents(int value) => new(value);
public static FixedPoint2 FromHundredths(int value) => new(value); public static FixedPoint2 FromHundredths(int value) => new(value);

View File

@@ -201,13 +201,13 @@ namespace Content.Shared.FixedPoint
} }
// Implicit operators ftw // Implicit operators ftw
public static implicit operator FixedPoint4(FixedPoint2 n) => New(n.Int()); public static implicit operator FixedPoint4(FixedPoint2 n) => new(n.Value * 100); // Offbrand
public static implicit operator FixedPoint4(float n) => New(n); public static implicit operator FixedPoint4(float n) => New(n);
public static implicit operator FixedPoint4(double n) => New(n); public static implicit operator FixedPoint4(double n) => New(n);
public static implicit operator FixedPoint4(int n) => New(n); public static implicit operator FixedPoint4(int n) => New(n);
public static implicit operator FixedPoint4(long n) => New(n); public static implicit operator FixedPoint4(long n) => New(n);
public static explicit operator FixedPoint2(FixedPoint4 n) => n.Int(); public static explicit operator FixedPoint2(FixedPoint4 n) => FixedPoint2.FromRaw((int)(n.Value / 100)); // Offbrand
public static explicit operator float(FixedPoint4 n) => n.Float(); public static explicit operator float(FixedPoint4 n) => n.Float();
public static explicit operator double(FixedPoint4 n) => n.Double(); public static explicit operator double(FixedPoint4 n) => n.Double();
public static explicit operator int(FixedPoint4 n) => n.Int(); public static explicit operator int(FixedPoint4 n) => n.Int();

View File

@@ -88,14 +88,16 @@ public sealed class HealthExaminableSystem : EntitySystem
msg.AddMarkupOrThrow(chosenLocStr); msg.AddMarkupOrThrow(chosenLocStr);
} }
// Offbrand: reordered the empty placeholder to after people have added to health examinable
// Anything else want to add on to this?
RaiseLocalEvent(uid, new HealthBeingExaminedEvent(msg), true);
if (msg.IsEmpty) if (msg.IsEmpty)
{ {
msg.AddMarkupOrThrow(Loc.GetString($"health-examinable-{component.LocPrefix}-none")); msg.AddMarkupOrThrow(Loc.GetString($"health-examinable-{component.LocPrefix}-none"));
} }
// Anything else want to add on to this?
RaiseLocalEvent(uid, new HealthBeingExaminedEvent(msg), true);
return msg; return msg;
} }
} }

View File

@@ -43,6 +43,9 @@ public sealed class IdentitySystem : EntitySystem
SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(OnSeeIdentity); SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(OnSeeIdentity);
SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<SeeIdentityAttemptEvent>>(OnRelaySeeIdentity); SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<SeeIdentityAttemptEvent>>(OnRelaySeeIdentity);
SubscribeLocalEvent<IdentityBlockerComponent, ItemMaskToggledEvent>(OnMaskToggled); SubscribeLocalEvent<IdentityBlockerComponent, ItemMaskToggledEvent>(OnMaskToggled);
SubscribeLocalEvent<IdentityBlockerComponent, Content.Shared.StatusEffectNew.StatusEffectRelayedEvent<SeeIdentityAttemptEvent>>(OnRelayedSeeIdentity); // Offbrand
SubscribeLocalEvent<IdentityBlockerComponent, Content.Shared.StatusEffectNew.StatusEffectAppliedEvent>((_, _, ev) => QueueIdentityUpdate(ev.Target)); // Offbrand
SubscribeLocalEvent<IdentityBlockerComponent, Content.Shared.StatusEffectNew.StatusEffectRemovedEvent>((_, _, ev) => QueueIdentityUpdate(ev.Target)); // Offbrand
SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<IdentityComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<IdentityComponent, ComponentInit>(OnComponentInit);
@@ -55,6 +58,15 @@ public sealed class IdentitySystem : EntitySystem
SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
} }
// Begin Offbrand
private void OnRelayedSeeIdentity(Entity<IdentityBlockerComponent> ent, ref Content.Shared.StatusEffectNew.StatusEffectRelayedEvent<SeeIdentityAttemptEvent> args)
{
var argsArgs = args.Args;
OnSeeIdentity(ent, ref argsArgs);
args.Args = argsArgs;
}
// End Offbrand
/// <summary> /// <summary>
/// Iterates through all identities that need to be updated. /// Iterates through all identities that need to be updated.
/// </summary> /// </summary>

View File

@@ -26,6 +26,7 @@ using Content.Shared.Verbs;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Shared.Medical.Cryogenics; namespace Content.Shared.Medical.Cryogenics;
@@ -105,7 +106,9 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
&& _solutionContainerQuery.TryComp(container, out var solutionContainerManagerComponent) && _solutionContainerQuery.TryComp(container, out var solutionContainerManagerComponent)
&& _solutionContainer.TryGetFitsInDispenser((container.Value, fitsInDispenserComponent, solutionContainerManagerComponent), && _solutionContainer.TryGetFitsInDispenser((container.Value, fitsInDispenserComponent, solutionContainerManagerComponent),
out var containerSolution, out _) out var containerSolution, out _)
&& _bloodstreamQuery.TryComp(patient, out var bloodstream)) && _bloodstreamQuery.TryComp(patient, out var bloodstream)
&& _solutionContainer.ResolveSolution(patient.Value, bloodstream.ChemicalSolutionName, ref bloodstream.ChemicalSolution, out var chemsSolution) // Offbrand
&& !chemsSolution.HasOverlapAtLeast(containerSolution.Value.Comp.Solution, cryoPod.BeakerTransferAmount * 2)) // Offbrand
{ {
var solutionToInject = _solutionContainer.SplitSolution(containerSolution.Value, cryoPod.BeakerTransferAmount); var solutionToInject = _solutionContainer.SplitSolution(containerSolution.Value, cryoPod.BeakerTransferAmount);
_bloodstream.TryAddToChemicals((patient.Value, bloodstream), solutionToInject); _bloodstream.TryAddToChemicals((patient.Value, bloodstream), solutionToInject);

View File

@@ -1,6 +1,7 @@
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Shared.Medical.SuitSensor; namespace Content.Shared.Medical.SuitSensor;
@@ -25,9 +26,12 @@ public sealed class SuitSensorStatus
public string JobIcon; public string JobIcon;
public List<string> JobDepartments; public List<string> JobDepartments;
public bool IsAlive; public bool IsAlive;
public int? TotalDamage; // Begin Offbrand Changes
public int? TotalDamageThreshold; // public int? TotalDamage;
public float? DamagePercentage => TotalDamageThreshold == null || TotalDamage == null ? null : TotalDamage / (float) TotalDamageThreshold; // public int? TotalDamageThreshold;
// public float? DamagePercentage => TotalDamageThreshold == null || TotalDamage == null ? null : TotalDamage / (float) TotalDamageThreshold;
public WoundableHealthAnalyzerData? WoundableData;
// End Offbrand Changes
public NetCoordinates? Coordinates; public NetCoordinates? Coordinates;
} }
@@ -63,10 +67,13 @@ public static class SuitSensorConstants
public const string NET_JOB_ICON = "jobIcon"; public const string NET_JOB_ICON = "jobIcon";
public const string NET_JOB_DEPARTMENTS = "jobDepartments"; public const string NET_JOB_DEPARTMENTS = "jobDepartments";
public const string NET_IS_ALIVE = "alive"; public const string NET_IS_ALIVE = "alive";
public const string NET_TOTAL_DAMAGE = "vitals"; // Begin Offbrand Changes
public const string NET_TOTAL_DAMAGE_THRESHOLD = "vitalsThreshold"; // public const string NET_TOTAL_DAMAGE = "vitals";
// public const string NET_TOTAL_DAMAGE_THRESHOLD = "vitalsThreshold";
public const string NET_COORDINATES = "coords"; public const string NET_COORDINATES = "coords";
public const string NET_WOUNDABLE_DATA = "woundableData";
public const string NET_SUIT_SENSOR_UID = "uid"; public const string NET_SUIT_SENSOR_UID = "uid";
// End Offbrand Changes
///Used by the CrewMonitoringServerSystem to send the status of all connected suit sensors to each crew monitor ///Used by the CrewMonitoringServerSystem to send the status of all connected suit sensors to each crew monitor
public const string NET_STATUS_COLLECTION = "suit-status-collection"; public const string NET_STATUS_COLLECTION = "suit-status-collection";

View File

@@ -22,6 +22,9 @@ using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Shared.FixedPoint; // Offbrand
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Shared.Medical.SuitSensors; namespace Content.Shared.Medical.SuitSensors;
@@ -31,7 +34,8 @@ public abstract class SharedSuitSensorSystem : EntitySystem
[Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; // [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; Offbrand - we don't need that
[Dependency] private readonly SharedWoundableHealthAnalyzerSystem _woundableHealthAnalyzer = default!; // Offbrand - we do need that
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
@@ -374,15 +378,17 @@ public abstract class SharedSuitSensorSystem : EntitySystem
if (TryComp(sensor.User.Value, out MobStateComponent? mobState)) if (TryComp(sensor.User.Value, out MobStateComponent? mobState))
isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState); isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState);
// get mob total damage // Begin Offbrand Removals
var totalDamage = 0; // // get mob total damage
if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable)) // var totalDamage = 0;
totalDamage = damageable.TotalDamage.Int(); // if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
// totalDamage = damageable.TotalDamage.Int();
// Get mob total damage crit threshold // // Get mob total damage crit threshold
int? totalDamageThreshold = null; // int? totalDamageThreshold = null;
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) // if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
totalDamageThreshold = critThreshold.Value.Int(); // totalDamageThreshold = critThreshold.Value.Int();
// End Offbrand Removals
// finally, form suit sensor status // finally, form suit sensor status
var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments); var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments);
@@ -392,14 +398,19 @@ public abstract class SharedSuitSensorSystem : EntitySystem
status.IsAlive = isAlive; status.IsAlive = isAlive;
break; break;
case SuitSensorMode.SensorVitals: case SuitSensorMode.SensorVitals:
status.IsAlive = isAlive; // Begin Offbrand Changes
status.TotalDamage = totalDamage; // status.IsAlive = isAlive;
status.TotalDamageThreshold = totalDamageThreshold; // status.TotalDamage = totalDamage;
break; // status.TotalDamageThreshold = totalDamageThreshold;
status.WoundableData = _woundableHealthAnalyzer.TakeSample(sensor.User.Value, withWounds: false);
goto case SuitSensorMode.SensorBinary;
// End Offbrand Changes
case SuitSensorMode.SensorCords: case SuitSensorMode.SensorCords:
status.IsAlive = isAlive; status.IsAlive = isAlive;
status.TotalDamage = totalDamage; // Begin Offbrand - don't duplicate code
status.TotalDamageThreshold = totalDamageThreshold; // status.TotalDamage = totalDamage;
// status.TotalDamageThreshold = totalDamageThreshold;
// End Offbrand - don't duplicate code
EntityCoordinates coordinates; EntityCoordinates coordinates;
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
@@ -420,7 +431,7 @@ public abstract class SharedSuitSensorSystem : EntitySystem
} }
status.Coordinates = GetNetCoordinates(coordinates); status.Coordinates = GetNetCoordinates(coordinates);
break; goto case SuitSensorMode.SensorVitals; // Offbrand - don't duplicate code
} }
return status; return status;
@@ -443,10 +454,14 @@ public abstract class SharedSuitSensorSystem : EntitySystem
[SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid, [SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid,
}; };
if (status.TotalDamage != null) // Begin Offbrand Changes
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage); // if (status.TotalDamage != null)
if (status.TotalDamageThreshold != null) // payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold); // if (status.TotalDamageThreshold != null)
// payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold);
if (status.WoundableData is { } woundable)
payload.Add(SuitSensorConstants.NET_WOUNDABLE_DATA, woundable);
// End Offbrand Changes
if (status.Coordinates != null) if (status.Coordinates != null)
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates); payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
@@ -474,15 +489,21 @@ public abstract class SharedSuitSensorSystem : EntitySystem
if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null; if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null;
// try get total damage and cords (optionals) // try get total damage and cords (optionals)
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage); // Begin Offbrand Changes
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold); // payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
// payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold);
payload.TryGetValue(SuitSensorConstants.NET_WOUNDABLE_DATA, out WoundableHealthAnalyzerData? woundableData);
// End Offbrand Changes
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords); payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords);
var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments) var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments)
{ {
IsAlive = isAlive.Value, IsAlive = isAlive.Value,
TotalDamage = totalDamage, // Begin Offbrand Changes
TotalDamageThreshold = totalDamageThreshold, // TotalDamage = totalDamage,
// TotalDamageThreshold = totalDamageThreshold,
WoundableData = woundableData,
// End Offbrand Changes
Coordinates = coords, Coordinates = coords,
}; };
return status; return status;

View File

@@ -1,3 +1,4 @@
using Content.Shared._Offbrand.Wounds; // Offbrand
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.MedicalScanner; namespace Content.Shared.MedicalScanner;
@@ -14,8 +15,9 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage
public bool? ScanMode; public bool? ScanMode;
public bool? Bleeding; public bool? Bleeding;
public bool? Unrevivable; public bool? Unrevivable;
public WoundableHealthAnalyzerData? WoundableData; // Offbrand
public HealthAnalyzerScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable) public HealthAnalyzerScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable, WoundableHealthAnalyzerData? woundableData) // Offbrand
{ {
TargetEntity = targetEntity; TargetEntity = targetEntity;
Temperature = temperature; Temperature = temperature;
@@ -23,6 +25,7 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage
ScanMode = scanMode; ScanMode = scanMode;
Bleeding = bleeding; Bleeding = bleeding;
Unrevivable = unrevivable; Unrevivable = unrevivable;
WoundableData = woundableData; // Offbrand
} }
} }

View File

@@ -20,6 +20,7 @@ public sealed class MobThresholdSystem : EntitySystem
{ {
SubscribeLocalEvent<MobThresholdsComponent, ComponentGetState>(OnGetState); SubscribeLocalEvent<MobThresholdsComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<MobThresholdsComponent, ComponentHandleState>(OnHandleState); SubscribeLocalEvent<MobThresholdsComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<MobThresholdsComponent, MapInitEvent>(MobThresholdMapInit); // Offbrand
SubscribeLocalEvent<MobThresholdsComponent, ComponentShutdown>(MobThresholdShutdown); SubscribeLocalEvent<MobThresholdsComponent, ComponentShutdown>(MobThresholdShutdown);
SubscribeLocalEvent<MobThresholdsComponent, ComponentStartup>(MobThresholdStartup); SubscribeLocalEvent<MobThresholdsComponent, ComponentStartup>(MobThresholdStartup);
@@ -441,6 +442,14 @@ public sealed class MobThresholdSystem : EntitySystem
UpdateAllEffects((target, thresholds, mobState, damageable), mobState.CurrentState); UpdateAllEffects((target, thresholds, mobState, damageable), mobState.CurrentState);
} }
// Begin Offbrand
private void MobThresholdMapInit(Entity<MobThresholdsComponent> ent, ref MapInitEvent args)
{
var overlayUpdate = new Content.Shared._Offbrand.Wounds.PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlayUpdate);
}
// End Offbrand
private void MobThresholdShutdown(EntityUid target, MobThresholdsComponent component, ComponentShutdown args) private void MobThresholdShutdown(EntityUid target, MobThresholdsComponent component, ComponentShutdown args)
{ {
if (component.TriggersAlerts) if (component.TriggersAlerts)

View File

@@ -214,4 +214,16 @@ public sealed partial class RevenantComponent : Component
[DataField("harvestingState")] [DataField("harvestingState")]
public string HarvestingState = "harvesting"; public string HarvestingState = "harvesting";
#endregion #endregion
/// <summary>
/// Offbrand - how much cold damage to deal on harvest
/// </summary>
[DataField]
public Content.Shared.Damage.DamageSpecifier HarvestDamage = new()
{
DamageDict = new()
{
{ "Cold", 200 },
}
};
} }

View File

@@ -39,6 +39,12 @@ public sealed partial class StatusEffectComponent : Component
[DataField] [DataField]
public bool Applied; public bool Applied;
/// <summary>
/// Offbrand - When the status effect started
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
public TimeSpan? StartedAt;
/// <summary> /// <summary>
/// Whitelist, by which it is determined whether this status effect can be imposed on a particular entity. /// Whitelist, by which it is determined whether this status effect can be imposed on a particular entity.
/// </summary> /// </summary>

View File

@@ -14,7 +14,7 @@ public sealed partial class StatusEffectsSystem
{ {
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, RejuvenateEvent>(RelayStatusEffectEvent); // SubscribeLocalEvent<StatusEffectContainerComponent, RejuvenateEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, RefreshMovementSpeedModifiersEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, RefreshMovementSpeedModifiersEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, UpdateCanMoveEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, UpdateCanMoveEvent>(RelayStatusEffectEvent);
@@ -26,6 +26,35 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent<StatusEffectContainerComponent, StunEndAttemptEvent>(RefRelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, StunEndAttemptEvent>(RefRelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, AccentGetEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, AccentGetEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.WoundGetDamageEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetWoundsWithSpaceEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetPainEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.HealWoundsEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetBleedLevelEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.PainSuppressionEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.BeforeDealBrainDamage>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.BeforeDepleteBrainOxygen>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.BeforeHealBrainDamage>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedVascularToneEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedLungFunctionEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedMetabolicRateEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedCardiacOutputEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedRespiratoryRateEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Weapons.RelayedGetMeleeDamageEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Weapons.RelayedGetMeleeAttackRateEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Weapons.RelayedGunRefreshModifiersEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Damage.Systems.ModifySlowOnDamageSpeedEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Eye.Blinding.Systems.GetBlurEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Eye.Blinding.Systems.CanSeeAttemptEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.IdentityManagement.Components.SeeIdentityAttemptEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Movement.Pulling.Events.PullStartedMessage>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Movement.Pulling.Events.PullStoppedMessage>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Weapons.Ranged.Events.SelfBeforeGunShotEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Chemistry.Hypospray.Events.SelfBeforeHyposprayInjectsEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Damage.Systems.DamageChangedEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Examine.ExaminedEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared.Verbs.GetVerbsEvent<Content.Shared.Verbs.AlternativeVerb>>(RelayStatusEffectEvent); // Offbrand
} }
private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct

View File

@@ -33,7 +33,7 @@ public sealed partial class StatusEffectsSystem : EntitySystem
SubscribeLocalEvent<StatusEffectContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted); SubscribeLocalEvent<StatusEffectContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
SubscribeLocalEvent<StatusEffectContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved); SubscribeLocalEvent<StatusEffectContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
SubscribeLocalEvent<RejuvenateRemovedStatusEffectComponent, StatusEffectRelayedEvent<RejuvenateEvent>>(OnRejuvenate); SubscribeLocalEvent<StatusEffectContainerComponent, RejuvenateEvent>(OnRejuvenate); // Offbrand
_containerQuery = GetEntityQuery<StatusEffectContainerComponent>(); _containerQuery = GetEntityQuery<StatusEffectContainerComponent>();
_effectQuery = GetEntityQuery<StatusEffectComponent>(); _effectQuery = GetEntityQuery<StatusEffectComponent>();
@@ -114,11 +114,19 @@ public sealed partial class StatusEffectsSystem : EntitySystem
Dirty(args.Entity, statusComp); Dirty(args.Entity, statusComp);
} }
private void OnRejuvenate(Entity<RejuvenateRemovedStatusEffectComponent> ent, // Begin Offbrand Changes
ref StatusEffectRelayedEvent<RejuvenateEvent> args) private void OnRejuvenate(Entity<StatusEffectContainerComponent> ent,
ref RejuvenateEvent args)
{ {
PredictedQueueDel(ent.Owner); if (!TryEffectsWithComp<RejuvenateRemovedStatusEffectComponent>(ent, out var effects))
return;
foreach (var effect in effects)
{
Del(effect);
}
} }
// End Offbrand Changes
/// <summary> /// <summary>
/// Applies the status effect, i.e. starts it after it has been added. Ensures delayed start times trigger when they should. /// Applies the status effect, i.e. starts it after it has been added. Ensures delayed start times trigger when they should.

View File

@@ -26,10 +26,11 @@ public sealed partial class ToolComponent : Component
/// Attempt event called *before* any do afters to see if the tool usage should succeed or not. /// Attempt event called *before* any do afters to see if the tool usage should succeed or not.
/// Raised on both the tool and then target. /// Raised on both the tool and then target.
/// </summary> /// </summary>
public sealed class ToolUseAttemptEvent(EntityUid user, float fuel) : CancellableEntityEventArgs public sealed class ToolUseAttemptEvent(EntityUid user, float fuel, EntityUid? target) : CancellableEntityEventArgs // Offbrand
{ {
public EntityUid User { get; } = user; public EntityUid User { get; } = user;
public float Fuel = fuel; public float Fuel = fuel;
public EntityUid? Target { get; } = target; // Offbrand
} }
/// <summary> /// <summary>

View File

@@ -251,7 +251,7 @@ public abstract partial class SharedToolSystem : EntitySystem
return false; return false;
// check if the tool allows being used // check if the tool allows being used
var beforeAttempt = new ToolUseAttemptEvent(user, fuel); var beforeAttempt = new ToolUseAttemptEvent(user, fuel, target); // Offbrand
RaiseLocalEvent(tool, beforeAttempt); RaiseLocalEvent(tool, beforeAttempt);
if (beforeAttempt.Cancelled) if (beforeAttempt.Cancelled)
return false; return false;

View File

@@ -250,6 +250,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeDamageEvent(uid, new(component.Damage * Damageable.UniversalMeleeDamageModifier), new(), user, component.ResistanceBypass); var ev = new GetMeleeDamageEvent(uid, new(component.Damage * Damageable.UniversalMeleeDamageModifier), new(), user, component.ResistanceBypass);
RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(uid, ref ev);
// Begin Offbrand
var relayed = new Content.Shared._Offbrand.Weapons.RelayedGetMeleeDamageEvent(ev);
RaiseLocalEvent(user, ref relayed);
ev = relayed.Args;
// End Offbrand
return DamageSpecifier.ApplyModifierSets(ev.Damage, ev.Modifiers); return DamageSpecifier.ApplyModifierSets(ev.Damage, ev.Modifiers);
} }
@@ -261,6 +267,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeAttackRateEvent(uid, component.AttackRate, 1, user); var ev = new GetMeleeAttackRateEvent(uid, component.AttackRate, 1, user);
RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(uid, ref ev);
// Begin Offbrand
var relayed = new Content.Shared._Offbrand.Weapons.RelayedGetMeleeAttackRateEvent(ev);
RaiseLocalEvent(user, ref relayed);
ev = relayed.Args;
// End Offbrand
return ev.Rate * ev.Multipliers; return ev.Rate * ev.Multipliers;
} }
@@ -283,6 +295,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeDamageEvent(uid, new(component.Damage * Damageable.UniversalMeleeDamageModifier), new(), user, component.ResistanceBypass); var ev = new GetMeleeDamageEvent(uid, new(component.Damage * Damageable.UniversalMeleeDamageModifier), new(), user, component.ResistanceBypass);
RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(uid, ref ev);
// Begin Offbrand
var relayed = new Content.Shared._Offbrand.Weapons.RelayedGetMeleeDamageEvent(ev);
RaiseLocalEvent(user, ref relayed);
ev = relayed.Args;
// End Offbrand
return ev.ResistanceBypass; return ev.ResistanceBypass;
} }

View File

@@ -562,6 +562,15 @@ public abstract partial class SharedGunSystem : EntitySystem
RaiseLocalEvent(gun, ref ev); RaiseLocalEvent(gun, ref ev);
// Begin Offbrand
if (Containers.TryGetContainingContainer(gun.Owner, out var container))
{
var relayed = new Content.Shared._Offbrand.Weapons.RelayedGunRefreshModifiersEvent(ev);
RaiseLocalEvent(container.Owner, ref relayed);
ev = relayed.Args;
}
// End Offbrand
if (comp.SoundGunshotModified != ev.SoundGunshot) if (comp.SoundGunshotModified != ev.SoundGunshot)
{ {
comp.SoundGunshotModified = ev.SoundGunshot; comp.SoundGunshotModified = ev.SoundGunshot;

View File

@@ -146,7 +146,7 @@ public sealed partial class ZombieComponent : Component
{ {
DamageDict = new() DamageDict = new()
{ {
{ "Slash", 13 }, // { "Slash", 13 }, Offbrand - slash is too fast a crit
{ "Piercing", 7 }, { "Piercing", 7 },
{ "Structural", 10 } { "Structural", 10 }
} }

View File

@@ -0,0 +1,15 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.Buckle;
/// <summary>
/// Applies a status effect to the strapped entity whilst it is strapped
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(StatusEffectOnStrapSystem))]
public sealed partial class StatusEffectOnStrapComponent : Component
{
[DataField(required: true)]
public EntProtoId StatusEffect;
}

View File

@@ -0,0 +1,56 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.Power;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.Buckle;
public sealed class StatusEffectOnStrapSystem : EntitySystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StatusEffectOnStrapComponent, StrappedEvent>(OnStrapped);
SubscribeLocalEvent<StatusEffectOnStrapComponent, UnstrappedEvent>(OnUnstrapped);
SubscribeLocalEvent<StatusEffectOnStrapComponent, PowerChangedEvent>(OnPowerChanged);
}
private void UpdateStatus(Entity<StrapComponent, StatusEffectOnStrapComponent> ent, EntityUid buckled)
{
var isBuckled = ent.Comp1.BuckledEntities.Contains(buckled);
var isPowered = _powerReceiver.IsPowered(ent.Owner);
if (isBuckled && isPowered)
{
if (!_statusEffects.HasStatusEffect(buckled, ent.Comp2.StatusEffect))
_statusEffects.TryUpdateStatusEffectDuration(buckled, ent.Comp2.StatusEffect, out _);
}
else
{
_statusEffects.TryRemoveStatusEffect(buckled, ent.Comp2.StatusEffect);
}
}
private void OnStrapped(Entity<StatusEffectOnStrapComponent> ent, ref StrappedEvent args)
{
UpdateStatus((ent.Owner, Comp<StrapComponent>(ent), ent.Comp), args.Buckle);
}
private void OnUnstrapped(Entity<StatusEffectOnStrapComponent> ent, ref UnstrappedEvent args)
{
UpdateStatus((ent.Owner, Comp<StrapComponent>(ent), ent.Comp), args.Buckle);
}
private void OnPowerChanged(Entity<StatusEffectOnStrapComponent> ent, ref PowerChangedEvent args)
{
var strap = Comp<StrapComponent>(ent);
foreach (var entity in strap.BuckledEntities)
{
UpdateStatus((ent.Owner, strap, ent.Comp), entity);
}
}
}

View File

@@ -0,0 +1,12 @@
using Content.Shared.FixedPoint;
namespace Content.Shared._Offbrand.Chemistry;
/// <summary>
/// Event raised before injecting in response to an event, to allow modifying how much is injected
/// </summary>
[ByRefEvent]
public struct BeforeInjectOnEventEvent(FixedPoint2 injectionAmount)
{
public FixedPoint2 InjectionAmount = injectionAmount;
}

View File

@@ -0,0 +1,56 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Content.Shared.Random.Helpers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class AdjustReagentGaussian : EntityEffectBase<AdjustReagentGaussian>
{
[DataField(required: true)]
public ProtoId<ReagentPrototype> Reagent;
[DataField(required: true)]
public double μ;
[DataField(required: true)]
public double σ;
public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var proto = prototype.Index(Reagent);
return Loc.GetString("entity-effect-guidebook-adjust-reagent-gaussian",
("chance", Probability),
("deltasign", Math.Sign(μ)),
("reagent", proto.LocalizedName),
("mu", Math.Abs(μ)),
("sigma", Math.Abs(σ)));
}
}
public sealed class AdjustReagentGaussianEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustReagentGaussian>
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
protected override void Effect(Entity<SolutionComponent> ent, ref EntityEffectEvent<AdjustReagentGaussian> args)
{
var seed = SharedRandomExtensions.HashCodeCombine((int)_timing.CurTick.Value, GetNetEntity(ent).Id);
var rand = new System.Random(seed);
var amount = rand.NextGaussian(args.Effect.μ, args.Effect.σ);
amount *= args.Scale;
var reagent = args.Effect.Reagent;
if (amount < 0)
_solutionContainer.RemoveReagent(ent, reagent, -amount);
else if (amount > 0)
_solutionContainer.TryAddReagent(ent, reagent, amount);
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityConditions;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed class BrainDamageEntityConditionSystem : EntityConditionSystem<BrainDamageComponent, BrainDamageCondition>
{
protected override void Condition(Entity<BrainDamageComponent> ent, ref EntityConditionEvent<BrainDamageCondition> args)
{
args.Result = ent.Comp.Damage >= args.Condition.Min && ent.Comp.Damage <= args.Condition.Max;
}
}
public sealed partial class BrainDamageCondition : EntityConditionBase<BrainDamageCondition>
{
[DataField]
public FixedPoint2 Max = FixedPoint2.MaxValue;
[DataField]
public FixedPoint2 Min = FixedPoint2.Zero;
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
{
return Loc.GetString("entity-condition-guidebook-brain-damage",
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
}

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