Offbrand medical (#3366)

* Offbrand medical

* what if we regrade

* zombies are mostly there thats it thats a wrap xd

* here's changeling

* some bonus gut punches

* start working on the guidebook

* fix rsi and yaml lints

* my agrichem so fits

* we stay rejuvenated

* my china so laked

* debrute

* fix suicide

* fix the suicide tests

* my surgery so requires laying down

* the guidebook continues

* READ KEB PAGES

* keb vascular recoupler

* read keb medicine

* fix yaml lint

* fix the EntityRemoveConstructionGraphStep

* fix overlay init

* scalpels are not a food tool

* return of the programmer art

* my line so nieuw

* boxes deserve veins too

* mrrrp yaml

* what if we redid brain damage alerts

* bloot pressure

* kill mannitol drowsiness

* get licensed

* my read so me

* get feedbacked nerd

* fine-tune the heart stoppage conditions

* cryostasis adjustments, guidebook adjustments, fix negative strain issues

* my surgery so table

* fix heart surgery and guidebook

* medicine & defibrillator pass

* iv bags and stands

* prefills

* janet gets very sidetracked

* mostly finished iv stuff

* what if we fixed the guidebook

* halve decapoid cryostasis

* my medicines so IV

* finetune cryostasis

* less logspam

* metabolism-aware iv stands and cryopods

* give people painkillers

* yaml lint real

* fix blood build

* finish rebase

* tidy up localization

* clean up my yaml beasties...

* soft curve after exceeding maximum damage

* husks/bonedeaths

Grabbag of Offmed fixes & improvements (#3461)

* CPR moment

* Mob AI fix

* Fix brain oxygenation not updating on regeneration

* sorry gamers you cannot resist the pull

* Troll combat abilities more in softcrit

praying rn (#3467)

dont have CPR be 50% (#3468)

Make offbrand murder easier to contend with (#3473)

* e

* disrupt people in softcrit when attacking them

* ok gamers we're gaming

* forgor

Hopefully final pass before Offbrand merge (#3475)

First pass of Offbrand adjustments (#3477)

Swap blood pressure values in health analyzer (#3476)

Systolic over diastolic

Co-authored-by: Kip <32859367+kipdotnet@users.noreply.github.com>

Offbrand pass 2: Mostly bugfixes (#3480)

Fix zeds causing PVS reloads (#3482)

Offbrand pass 3: I hate surgery I hate surgery I hate surgery I (#3481)

* set up surgery ui

* test fail real

Pain/braingasps (#3487)

Offmed bundle 5 - the evil one (#3489)

* Evil cavity surgery

* les borgues

* nicotine moment

* epinephrine RNG

* legalese

* test fail real

* ok jamers cope with c4

Pass 6
This commit is contained in:
pathetic meowmeow
2025-09-13 23:15:18 -04:00
committed by Janet Blackquill
parent 398c8df343
commit 8894eca118
451 changed files with 16350 additions and 347 deletions

View File

@@ -1,64 +1,94 @@
<!-- offbrand completely redid this file, use offbrand's version if there are merge conflicts -->
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MaxHeight="525"
MinWidth="300">
MaxHeight="725"
MinWidth="600">
<ScrollContainer
Margin="5 5 5 5"
ReturnMeasure="True"
VerticalExpand="True">
<BoxContainer
Name="RootContainer"
VerticalExpand="True"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<BoxContainer Orientation="Horizontal">
<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" />
Name="LeftContainer"
VerticalExpand="True"
Orientation="Vertical"
MinWidth="300">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<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>
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
<PanelContainer StyleClasses="LowDivider" />
<GridContainer Margin="0 5 0 0" Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<RichTextLabel Name="StatusLabel" />
<Label Name="BrainHealthText" Text="{Loc 'health-analyzer-window-entity-brain-health-text'}" />
<RichTextLabel Name="BrainHealthLabel" />
<Label Name="BloodPressureText" Text="{Loc 'health-analyzer-window-entity-blood-pressure-text'}" />
<RichTextLabel Name="BloodPressureLabel" />
<Label Name="BloodOxygenationText" Text="{Loc 'health-analyzer-window-entity-blood-oxygenation-text'}" />
<RichTextLabel Name="BloodOxygenationLabel" />
<Label Name="BloodFlowText" Text="{Loc 'health-analyzer-window-entity-blood-flow-text'}" />
<RichTextLabel Name="BloodFlowLabel" />
<Label Name="HeartRateText" Text="{Loc 'health-analyzer-window-entity-heart-rate-text'}" />
<RichTextLabel Name="HeartRateLabel" />
<Label Name="HeartHealthText" Text="{Loc 'health-analyzer-window-entity-heart-health-text'}" />
<RichTextLabel Name="HeartHealthLabel" />
<Label Name="BloodText" Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
<RichTextLabel Name="BloodLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
<RichTextLabel Name="TemperatureLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<RichTextLabel Name="DamageLabel" />
</GridContainer>
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<PanelContainer Name="ReagentsDivider" Visible="False" StyleClasses="LowDivider" />
<GridContainer Margin="0 5 0 0" Columns="2">
<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 Name="ReagentsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalExpand="True" />
</BoxContainer>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<PanelContainer StyleClasses="LowDivider" VerticalExpand="True" Margin="0 0 0 0" SetWidth="2" />
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
Name="RightContainer"
VerticalExpand="True"
Orientation="Vertical"
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>
</ScrollContainer>
</controls:FancyWindow>

View File

@@ -110,7 +110,7 @@ namespace Content.Client.HealthAnalyzer.UI
// 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;
AlertsContainer.Visible = showAlerts;
@@ -134,6 +134,115 @@ namespace Content.Client.HealthAnalyzer.UI
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}"), ("rating", woundable.BrainHealthRating));
HeartHealthText.Visible = true;
HeartHealthLabel.Visible = true;
HeartHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-health-value", ("value", $"{woundable.HeartHealth * 100:F1}"), ("rating", woundable.HeartHealthRating));
HeartRateText.Visible = true;
HeartRateLabel.Visible = true;
HeartRateLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-rate-value", ("value", woundable.HeartRate), ("rating", woundable.HeartRateRating));
BloodOxygenationText.Visible = true;
BloodOxygenationLabel.Visible = true;
BloodOxygenationLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-oxygenation-value", ("value", $"{woundable.BloodOxygenation * 100:F1}"), ("rating", woundable.BloodOxygenationRating));
BloodFlowText.Visible = true;
BloodFlowLabel.Visible = true;
BloodFlowLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-flow-value", ("value", $"{woundable.BloodFlow * 100:F1}"), ("rating", woundable.BloodFlowRating));
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), ("rating", woundable.BloodPressureRating));
BloodLabel.Visible = false;
BloodText.Visible = false;
}
else
{
BrainHealthLabel.Visible = false;
BloodPressureLabel.Visible = false;
BloodOxygenationLabel.Visible = false;
HeartRateLabel.Visible = false;
HeartHealthLabel.Visible = false;
BloodFlowLabel.Visible = false;
BrainHealthText.Visible = false;
BloodPressureText.Visible = false;
BloodOxygenationText.Visible = false;
BloodFlowText.Visible = false;
HeartRateText.Visible = false;
HeartHealthText.Visible = false;
BloodLabel.Visible = true;
BloodText.Visible = true;
}
// End Offbrand
// Damage Groups
var damageSortedGroups =
@@ -200,6 +309,10 @@ namespace Content.Client.HealthAnalyzer.UI
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
// Begin Offbrand
NoDamagesText.Visible = GroupsContainer.ChildCount == 0;
// End Offbrand
}
private Texture GetTexture(string texture)

View File

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

View File

@@ -89,7 +89,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (existingSensor.Coordinates != null && sensor.Coordinates == null)
continue;
if (existingSensor.DamagePercentage != null && sensor.DamagePercentage == null)
if (existingSensor.WoundableData != null && sensor.WoundableData == null) // Offbrand
continue;
}
@@ -237,16 +237,24 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
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);
// if (index >= 5)
// specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
// else
// specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "health" + index);
// }
// End Offbrand Removals
// Begin Offbrand Additions
if (sensor.WoundableData?.AnyVitalCritical == true)
{
var index = MathF.Round(4f * sensor.DamagePercentage.Value);
if (index >= 5)
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
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"), "critical");
}
// End Offbrand Additions
// Status icon
var statusIcon = new AnimatedTextureRect
@@ -304,6 +312,26 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
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), ("rating", woundable.HeartRateRating)) });
var (systolic, diastolic) = woundable.BloodPressure;
vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-blood-pressure", ("systolic", systolic), ("diastolic", diastolic), ("rating", woundable.BloodPressureRating)) });
vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-oxygenation", ("oxygenation", $"{woundable.BloodOxygenation * 100:F1}"), ("rating", woundable.BloodOxygenationRating)) });
}
mainContainer.AddChild(vitalsContainer);
// End Offbrand Additions
// Add user coordinates to the navmap
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!;
private EntityHealthBarOverlay _overlay = default!;
private Content.Client._Offbrand.Overlays.HeartrateOverlay _heartrate = default!; // Offbrand
public override void Initialize()
{
@@ -24,6 +25,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
SubscribeLocalEvent<ShowHealthBarsComponent, AfterAutoHandleStateEvent>(OnHandleState);
_overlay = new(EntityManager, _prototype);
_heartrate = new();
}
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
@@ -43,12 +45,19 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
}
_overlay.StatusIcon = comp.HealthStatusIcon;
_heartrate.StatusIcon = comp.HealthStatusIcon; // Offbrand
}
if (!_overlayMan.HasOverlay<EntityHealthBarOverlay>())
{
_overlayMan.AddOverlay(_overlay);
}
// Begin Offbrand
if (!_overlayMan.HasOverlay<Content.Client._Offbrand.Overlays.HeartrateOverlay>())
{
_overlayMan.AddOverlay(_heartrate);
}
// End Offbrand
}
protected override void DeactivateInternal()
@@ -57,5 +66,6 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
_overlay.DamageContainers.Clear();
_overlayMan.RemoveOverlay(_overlay);
_overlayMan.RemoveOverlay(_heartrate); // Offbrand
}
}

View File

@@ -7,6 +7,8 @@ using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
using System.Linq;
using Content.Shared._Offbrand.Wounds; // Offbrand
using Content.Shared.Mobs; // Offbrand
namespace Content.Client.Overlays;
@@ -60,8 +62,40 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
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)
{
if (TryComp<BrainDamageComponent>(entity, out var brain) &&
TryComp<BrainDamageThresholdsComponent>(entity, out var thresholds))
{
return DecideBrainHealthIcons((entity.Owner, brain, thresholds));
}
var damageableComponent = entity.Comp;
if (damageableComponent.DamageContainerID == null ||

View File

@@ -10,6 +10,8 @@ using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Player;
using System.Linq; // Offbrand
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Client.UserInterface.Systems.DamageOverlays;
@@ -20,6 +22,9 @@ public sealed class DamageOverlayUiController : UIController
[Dependency] private readonly IPlayerManager _playerManager = 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!;
public override void Initialize()
@@ -29,6 +34,7 @@ public sealed class DamageOverlayUiController : UIController
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<MobThresholdChecked>(OnThresholdCheck);
SubscribeLocalEvent<PotentiallyUpdateDamageOverlay>(OnPotentiallyUpdateDamageOverlay); // Offbrand
}
private void OnPlayerAttach(LocalPlayerAttachedEvent args)
@@ -69,11 +75,62 @@ public sealed class DamageOverlayUiController : UIController
_overlay.CritLevel = 0f;
_overlay.PainLevel = 0f;
_overlay.OxygenLevel = 0f;
_overlay.AlwaysRenderAll = false; // Offbrand
}
//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)
{
// Begin Offbrand Changes
TryUpdateSimpleOverlays(entity, mobState, damageable, thresholds);
TryUpdateWoundableOverlays(entity);
}
private void OnPotentiallyUpdateDamageOverlay(ref PotentiallyUpdateDamageOverlay 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.BloodOxygenation((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) ||
thresholds == null && !EntityManager.TryGetComponent(entity, out thresholds) ||
damageable == null && !EntityManager.TryGetComponent(entity, out damageable))

View File

@@ -25,6 +25,8 @@ public sealed class DamageOverlay : Overlay
public MobState State = MobState.Alive;
public bool AlwaysRenderAll = false; // Offbrand
/// <summary>
/// Handles the red pulsing overlay
/// </summary>
@@ -52,6 +54,7 @@ public sealed class DamageOverlay : Overlay
{
// TODO: Replace
IoCManager.InjectDependencies(this);
ZIndex = -5; // Offbrand
_oxygenShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
_critShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
_bruteShader = _prototypeManager.Index(CircleMaskShader).InstanceUnique();
@@ -138,39 +141,6 @@ public sealed class DamageOverlay : Overlay
// Makes debugging easier don't @ me
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;
if (level > 0f)
@@ -217,6 +187,41 @@ public sealed class DamageOverlay : Overlay
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;
if (level > 0f)

View File

@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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,106 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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 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");
public HeartrateOverlay()
{
IoCManager.InjectDependencies(this);
_transform = _entityManager.System<SharedTransformSystem>();
_sprite = _entityManager.System<SpriteSystem>();
_statusIcon = _entityManager.System<StatusIconSystem>();
}
private SpriteSpecifier GetIcon(Entity<HeartrateComponent> ent)
{
var strain = ent.Comp.Strain;
return strain.Double() switch {
_ when !ent.Comp.Running => HudStopped,
>= 4 => HudDanger,
>= 3 => HudBad,
>= 2 => HudPoor,
>= 1 => HudOkay,
_ => HudGood,
};
}
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 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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();
_menu = this.CreateWindow<SurgeryGuideMenu>();
_menu.OnSurgerySelected += OnSurgerySelected;
_menu.OnCleanUp += OnCleanUp;
}
private void OnSurgerySelected(ProtoId<ConstructionPrototype> surgery)
{
SendPredictedMessage(new SurgeryGuideStartSurgeryMessage(surgery));
}
private void OnCleanUp()
{
SendPredictedMessage(new SurgeryGuideStartCleanupMessage());
}
}

View File

@@ -0,0 +1,29 @@
<!--
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<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,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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 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;
Populate();
}
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 != "Surgery")
continue;
listData.Add(new SurgeryListData(proto));
}
listData.Sort((
a, b) => string.Compare(Loc.GetString(a.Construction.SetName!.Value), Loc.GetString(b.Construction.SetName!.Value), StringComparison.InvariantCulture));
PossibleSurgeries.PopulateList(listData);
}
}

View File

@@ -0,0 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Surgery;
namespace Content.Client._Offbrand.Surgery;
public sealed class SurgeryGuideTargetSystem : SharedSurgeryGuideTargetSystem;

View File

@@ -0,0 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
namespace Content.Client._Offbrand.Wounds;
public sealed class WoundableHealthAnalyzerSystem : SharedWoundableHealthAnalyzerSystem;

View File

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

View File

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

View File

@@ -87,7 +87,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3));
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
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));
});
@@ -104,7 +104,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2));
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
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));
});

View File

@@ -80,6 +80,24 @@ namespace Content.Server.Body.Components
/// </summary>
[DataField("groups")]
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>

View File

@@ -28,6 +28,7 @@ namespace Content.Server.Body.Systems
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly Content.Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!;
private EntityQuery<OrganComponent> _organQuery;
private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
@@ -128,7 +129,7 @@ namespace Content.Server.Body.Systems
if (solutionEntityUid is null
|| soln 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;
}
@@ -138,6 +139,7 @@ namespace Content.Server.Body.Systems
var list = solution.Contents.ToArray();
_random.Shuffle(list);
var metabolized = new HashSet<ProtoId<ReagentPrototype>>();
int reagents = 0;
foreach (var (reagent, quantity) in list)
{
@@ -155,10 +157,12 @@ namespace Content.Server.Body.Systems
continue;
}
// Begin Offbrand - No we're not
// we're done here entirely if this is true
if (reagents >= ent.Comp1.MaxReagentsProcessable)
return;
// if (reagents >= ent.Comp1.MaxReagentsProcessable)
// return;
// End Offbrand
metabolized.Add(reagent.Prototype); // Offbrand
// loop over all our groups and see which ones apply
if (ent.Comp1.MetabolismGroups is null)
@@ -188,6 +192,16 @@ namespace Content.Server.Body.Systems
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale);
// Begin Offbrand
foreach (var effect in entry.StatusEffects)
{
if (!effect.ShouldApplyStatusEffect(args))
_statusEffects.TryRemoveStatusEffect(actualEntity, effect.StatusEffect);
else
_statusEffects.TryUpdateStatusEffectDuration(actualEntity, effect.StatusEffect, out _);
}
// End Offbrand
// do all effects, if conditions apply
foreach (var effect in entry.Effects)
{
@@ -213,13 +227,81 @@ namespace Content.Server.Body.Systems
// remove a certain amount of reagent
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
reagents += 1;
// Begin Offbrand
if (!ent.Comp1.Metabolites.ContainsKey(reagent.Prototype))
ent.Comp1.Metabolites[reagent.Prototype] = 0;
ent.Comp1.Metabolites[reagent.Prototype] += removed;
// End Offbrand
}
}
// 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.TryIndex(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);
}
}

View File

@@ -95,7 +95,10 @@ public sealed class RespiratorSystem : EntitySystem
}
}
if (respirator.Saturation < respirator.SuffocationThreshold)
// Begin Offbrand - Respirators gasp when their heart has stopped
var isSuffocating = respirator.Saturation < respirator.SuffocationThreshold;
var shouldGaspFromHeart = TryComp<Content.Shared._Offbrand.Wounds.HeartrateComponent>(uid, out var heartrate) && !heartrate.Running;
if (isSuffocating || shouldGaspFromHeart)
{
if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown)
{
@@ -106,10 +109,14 @@ public sealed class RespiratorSystem : EntitySystem
ignoreActionBlocker: true);
}
TakeSuffocationDamage((uid, respirator));
respirator.SuffocationCycles += 1;
continue;
if (isSuffocating)
{
TakeSuffocationDamage((uid, respirator));
respirator.SuffocationCycles += 1;
continue;
}
}
// End Offbrand - Respirators gasp when their heart has stopped
StopSuffocation((uid, respirator));
respirator.SuffocationCycles = 0;

View File

@@ -59,6 +59,7 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly Content.Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!; // Offbrand
private bool _loocEnabled = true;
private bool _deadLoocEnabled;
@@ -209,6 +210,13 @@ public sealed partial class ChatSystem : SharedChatSystem
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 shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation);
// 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;
foreach (var targetBloodstream in targetBloodstreams)
{
// Begin Offbrand
var beforeInject = new Content.Shared._Offbrand.Chemistry.BeforeInjectOnEventEvent { InjectionAmount = volumePerBloodstream };
RaiseLocalEvent(targetBloodstream, ref beforeInject);
if (beforeInject.InjectionAmount < 0)
continue;
// End Offbrand
// 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
if (_bloodstream.TryAddToChemicals(targetBloodstream.AsNullable(), individualInjection))
anySuccess = true;

View File

@@ -87,6 +87,14 @@ namespace Content.Server.Construction
{
args.PushMarkup(Loc.GetString("deconstruction-header-text") + "\n");
}
// Begin Offbrand
else if (target.SurgeryName is { } surgery)
{
args.PushMarkup(Loc.GetString(
"construction-component-to-perform-header",
("name", Loc.GetString(surgery))) + "\n");
}
// End Offbrand
else
{
args.PushMarkup(Loc.GetString(
@@ -165,8 +173,11 @@ namespace Content.Server.Construction
// Initial construction header.
new()
{
Localization = construction.Type == ConstructionType.Structure
? "construction-presenter-to-build" : "construction-presenter-to-craft",
Localization = construction.Type switch {
ConstructionType.Structure => "construction-presenter-to-build",
ConstructionType.Surgery => "construction-presenter-to-surgery",
_ => "construction-presenter-to-craft",
},
EntryNumber = step,
}
};
@@ -183,7 +194,7 @@ namespace Content.Server.Construction
return null;
// First steps are handled specially.
if (step == 1)
if (step == 1 && construction.Category != "Surgery") // Offbrand
{
foreach (var graphStep in edge.Steps)
{

View File

@@ -66,7 +66,7 @@ namespace Content.Server.Construction
// If we're currently in an edge, we'll let the edge handle or validate the interaction.
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...
if (!validation && result is HandleResult.False && construction.StepIndex == 0)
@@ -100,7 +100,7 @@ namespace Content.Server.Construction
for (var i = 0; i < node.Edges.Count; 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.
// In the case of DoAfter, it's only allowed to modify the waiting flag and the current edge index.
@@ -116,7 +116,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 (construction.Node != node.Name)
if (completed) // Offbrand
return result;
// If we're still in the same node, that means we entered the edge and it's still not done.
@@ -138,8 +138,9 @@ namespace Content.Server.Construction
/// <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>
/// <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))
return HandleResult.False;
@@ -180,6 +181,7 @@ namespace Content.Server.Construction
// We change the node now.
ChangeNode(uid, user, edge.Target, true, construction);
completed = true; // Offbrand
}
return HandleResult.True;

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ using Content.Shared.Timing;
using Content.Shared.Toggleable;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Medical;
@@ -111,10 +112,15 @@ public sealed class DefibrillatorSystem : EntitySystem
if (!_powerCell.HasActivatableCharge(uid, user: user))
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;
if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState))
if (!targetCanBeAlive && heartrate is null && !component.CanDefibCrit && _mobState.IsCritical(target, mobState)) // Offbrand
return false;
return true;
@@ -175,8 +181,9 @@ public sealed class DefibrillatorSystem : EntitySystem
if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
return;
var hasDefib = TryComp<HeartDefibrillatableComponent>(target, out var heartDefibrillatable); // Offbrand
if (!TryComp<MobStateComponent>(target, out var mob) ||
!TryComp<MobThresholdsComponent>(target, out var thresholds))
(!TryComp<MobThresholdsComponent>(target, out var thresholds) && !hasDefib)) // Offbrand
return;
_audio.PlayPvs(component.ZapSound, uid);
@@ -194,11 +201,28 @@ public sealed class DefibrillatorSystem : EntitySystem
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"),
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),
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
{
if (_mobState.IsDead(target, mob))

View File

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

View File

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

View File

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

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Utility;
using Robust.Shared.Map.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Revenant.EntitySystems;
@@ -46,6 +47,7 @@ public sealed partial class RevenantSystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly HealthRankingSystem _healthRanking = default!; // Offbrand
private static readonly ProtoId<TagPrototype> WindowTag = "Window";
@@ -143,7 +145,7 @@ public sealed partial class RevenantSystem
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);
return;
@@ -201,7 +203,7 @@ public sealed partial class RevenantSystem
if (!HasComp<MobStateComponent>(args.Args.Target))
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);
component.EssenceRegenCap += component.MaxEssenceUpgradeAmount;
@@ -209,10 +211,12 @@ public sealed partial class RevenantSystem
//KILL THEMMMM
if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage))
return;
// Begin Offbrand Removals
// if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage))
// return;
// End Offbrand Removals
DamageSpecifier dspec = new();
dspec.DamageDict.Add("Cold", damage.Value);
dspec.DamageDict.Add("Cold", component.HarvestColdDamage); // Offbrand - use a fixed amount of cold
_damage.TryChangeDamage(args.Args.Target, dspec, true, origin: uid);
args.Handled = true;

View File

@@ -44,6 +44,7 @@ using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Roles;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Zombies;
@@ -77,6 +78,8 @@ public sealed partial class ZombieSystem
private static readonly ProtoId<NpcFactionPrototype> ZombieFaction = "Zombie";
private static readonly string MindRoleZombie = "MindRoleZombie";
private static readonly List<ProtoId<AntagPrototype>> BannableZombiePrototypes = ["Zombie"];
private static readonly EntProtoId AddOnWoundableZombified = "AddOnWoundableZombified"; // Offbrand
private static readonly EntProtoId AddOnAnyZombified = "AddOnAnyZombified"; // Offbrand
/// <summary>
/// Handles an entity turning into a zombie when they die or go into crit
@@ -144,6 +147,36 @@ public sealed partial class ZombieSystem
RemComp<ComplexInteractionComponent>(target);
RemComp<SentienceTargetComponent>(target);
// Begin Offbrand
if (RemComp<WoundableComponent>(target))
{
RemComp<HeartrateComponent>(target);
RemComp<HeartDefibrillatableComponent>(target);
RemComp<HeartStopOnHypovolemiaComponent>(target);
RemComp<HeartStopOnHighStrainComponent>(target);
RemComp<HeartStopOnBrainHealthComponent>(target);
RemComp<PainComponent>(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);
var entProto = _protoManager.Index(AddOnWoundableZombified);
EntityManager.RemoveComponents(target, entProto.Components);
EntityManager.AddComponents(target, entProto.Components);
}
// End Offbrand
//funny voice
var accentType = "zombie";
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
@@ -227,11 +260,18 @@ public sealed partial class ZombieSystem
//The zombie gets the assigned damage weaknesses and strengths
_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.
//NOTE: they are supposed to bleed, just not take damage
_bloodstream.SetBloodLossThreshold(target, 0f);
//Give them zombie blood
_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.
_inventory.TryUnequip(target, "gloves", true, true);
@@ -256,6 +296,11 @@ public sealed partial class ZombieSystem
_faction.ClearFactions(target, dirty: false);
_faction.AddFaction(target, ZombieFaction);
// Begin Offbrand
var rejuv = new Content.Shared.Rejuvenate.RejuvenateEvent();
RaiseLocalEvent(target, rejuv);
// End Offbrand
//gives it the funny "Zombie ___" name.
_nameMod.RefreshNameModifiers(target);

View File

@@ -26,6 +26,7 @@ using Content.Shared.Zombies;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Zombies
{
@@ -43,6 +44,7 @@ namespace Content.Server.Zombies
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly Content.Shared._Offbrand.Wounds.BrainDamageSystem _brainDamage = default!; // Offbrand
public readonly ProtoId<NpcFactionPrototype> Faction = "Zombie";
@@ -138,6 +140,7 @@ namespace Content.Server.Zombies
: 1f;
_damageable.TryChangeDamage(uid, comp.Damage * multiplier, true, false, damage);
_brainDamage.TryChangeBrainDamage(uid, multiplier / 2f); // Offbrand
}
// Heal the zombified
@@ -255,7 +258,7 @@ namespace Content.Server.Zombies
{
args.BonusDamage = -args.BaseDamage;
}
else
else if (!HasComp<WoundableComponent>(entity)) // Offbrand
{
if (!HasComp<ZombieImmuneComponent>(entity) && !HasComp<NonSpreaderZombieComponent>(args.User) && _random.Prob(GetZombieInfectionChance(entity, component)))
{
@@ -264,7 +267,7 @@ namespace Content.Server.Zombies
}
}
if (_mobState.IsIncapacitated(entity, mobState) && !HasComp<ZombieComponent>(entity) && !HasComp<ZombieImmuneComponent>(entity))
if (_mobState.IsIncapacitated(entity, mobState) && !HasComp<ZombieComponent>(entity) && !HasComp<ZombieImmuneComponent>(entity) && !HasComp<WoundableComponent>(entity)) // Offbrand
{
ZombifyEntity(entity);
args.BonusDamage = -args.BaseDamage;

View File

@@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Server.Body.Components;
using Content.Shared._Offbrand.EntityEffects;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
namespace Content.Server._Offbrand.EntityEffects;
public sealed class MetaboliteThresholdSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CheckEntityEffectConditionEvent<MetaboliteThreshold>>(OnCheckMetaboliteThreshold);
}
private void OnCheckMetaboliteThreshold(ref CheckEntityEffectConditionEvent<MetaboliteThreshold> args)
{
if (args.Args is not EntityEffectReagentArgs reagentArgs)
throw new NotImplementedException();
var reagent = args.Condition.Reagent;
if (reagent == null)
reagent = reagentArgs.Reagent?.ID;
if (reagent is not { } metaboliteReagent)
{
args.Result = true;
return;
}
if (!TryComp<MetabolizerComponent>(reagentArgs.OrganEntity, out var metabolizer))
{
args.Result = true;
return;
}
var metabolites = metabolizer.Metabolites;
var quant = FixedPoint2.Zero;
metabolites.TryGetValue(metaboliteReagent, out quant);
if (args.Condition.IncludeBloodstream && reagentArgs.Source != null)
{
quant += reagentArgs.Source.GetTotalPrototypeQuantity(metaboliteReagent);
}
args.Result = quant >= args.Condition.Min && quant <= args.Condition.Max;
}
}

View File

@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Server.Zombies;
using Content.Shared._Offbrand.EntityEffects;
using Content.Shared.EntityEffects;
namespace Content.Server._Offbrand.EntityEffects;
public sealed class ZombifySystem : EntitySystem
{
[Dependency] private readonly ZombieSystem _zombie = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ExecuteEntityEffectEvent<Zombify>>(OnExecuteZombify);
}
private void OnExecuteZombify(ref ExecuteEntityEffectEvent<Zombify> args)
{
_zombie.ZombifyEntity(args.Args.TargetEntity);
}
}

View File

@@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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,176 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Server.Body.Components;
using Content.Server.Chat.Systems;
using Content.Server.EUI;
using Content.Shared._Offbrand.MMI;
using Content.Shared._Offbrand.Wounds;
using Content.Shared.Body.Systems;
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 ChatSystem _chat = 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 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,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.Construction;
using Content.Server.Construction;
using Robust.Shared.Prototypes;
namespace Content.Server._Offbrand.Surgery;
[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,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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.TryIndex(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,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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
{
[DataField(required: true)]
public EntProtoId LathePrototype;
[DataField(required: true)]
public EntProtoId EntityToSpawn;
}

View File

@@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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,50 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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);
}
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);
}
}

View File

@@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Server.Temperature.Components;
using Content.Shared._Offbrand.Wounds;
using Content.Shared.Body.Events;
using Content.Shared.Body.Systems;
using Content.Shared.Medical.Cryogenics;
using Content.Shared.Temperature;
namespace Content.Server._Offbrand.Wounds;
public sealed class CryostasisFactorSystem : EntitySystem
{
[Dependency] protected readonly SharedMetabolizerSystem _metabolizer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CryostasisFactorComponent, OnTemperatureChangeEvent>(OnTemperatureChange);
SubscribeLocalEvent<CryostasisFactorComponent, GetMetabolicMultiplierEvent>(OnGetMetabolicMultiplier);
}
private void OnTemperatureChange(Entity<CryostasisFactorComponent> ent, ref OnTemperatureChangeEvent args)
{
_metabolizer.UpdateMetabolicMultiplier(ent);
}
private void OnGetMetabolicMultiplier(Entity<CryostasisFactorComponent> ent, ref GetMetabolicMultiplierEvent args)
{
if (!TryComp<TemperatureComponent>(ent, out var temp))
return;
args.Multiplier *= Math.Max(ent.Comp.TemperatureCoefficient * temp.CurrentTemperature + ent.Comp.TemperatureConstant, 1);
}
}

View File

@@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Server.Body.Components;
using Content.Shared._Offbrand.Wounds;
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 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);
}
}
return ret;
}
}

View File

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

View File

@@ -77,11 +77,14 @@ public abstract class SharedBloodstreamSystem : EntitySystem
TryModifyBloodLevel((uid, bloodstream), bloodstream.BloodRefreshAmount);
}
// Begin Offbrand
var bleedLevel = EffectiveBleedLevel((uid, bloodstream));
// Removes blood from the bloodstream based on bleed amount (bleed rate)
// 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);
// Blood is removed from the bloodstream at a 1-1 rate with the bleed amount
@@ -91,9 +94,18 @@ public abstract class SharedBloodstreamSystem : EntitySystem
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.
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.
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
_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.
_damageableSystem.TryChangeDamage(
@@ -241,26 +253,28 @@ public abstract class SharedBloodstreamSystem : EntitySystem
/// </summary>
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
{
var bleedLevel = EffectiveBleedLevel(ent); // Offbrand
// 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.AddMarkupOrThrow(Loc.GetString("bloodstream-component-massive-bleeding", ("target", ent.Owner)));
}
// 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.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.
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.25f)
else if (bleedLevel > ent.Comp.MaxBleedAmount * 0.25f) // Offbrand
{
args.Message.PushNewline();
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
}
// 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.AddMarkupOrThrow(Loc.GetString("bloodstream-component-slight-bleeding", ("target", ent.Owner)));
@@ -399,6 +413,19 @@ public abstract class SharedBloodstreamSystem : EntitySystem
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>
/// Tries to make an entity bleed more or less.
/// </summary>
@@ -412,13 +439,17 @@ public abstract class SharedBloodstreamSystem : EntitySystem
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);
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);
}
// End Offbrand
return true;
}

View File

@@ -16,6 +16,7 @@ public sealed partial class StrapComponent : Component
/// The entities that are currently buckled to this strap.
/// </summary>
[DataField, AutoNetworkedField]
[Access(typeof(SharedBuckleSystem), Other = AccessPermissions.ReadExecute)] // Offbrand
public HashSet<EntityUid> BuckledEntities = new();
/// <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;
if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, logMissing: false))

View File

@@ -278,6 +278,12 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("metabolismRate")]
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>
/// A list of effects to apply when these reagents are metabolized.
/// </summary>
@@ -290,12 +296,51 @@ namespace Content.Shared.Chemistry.Reagent
return new ReagentEffectsGuideEntry(MetabolismRate,
Effects
.Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
.Concat(StatusEffects.Select(x => x.Describe(prototype, entSys))) // Offbrand
.Where(x => x is not null)
.Select(x => x!)
.ToArray());
}
}
// Begin Offbrand
[DataDefinition]
public sealed partial class ReagentStatusEffectEntry
{
[DataField]
public EntityEffectCondition[]? Conditions;
[DataField]
public EntProtoId StatusEffect;
public bool ShouldApplyStatusEffect(EntityEffectBaseArgs args)
{
if (Conditions != null)
{
foreach (var cond in Conditions)
{
if (!cond.Condition(args))
return false;
}
}
return true;
}
public string? Describe(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (!prototype.TryIndex(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.GuidebookExplanation(prototype)).ToList() ??
new List<string>())));
}
}
// End Offbrand
[Serializable, NetSerializable]
public struct ReagentEffectsGuideEntry
{

View File

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

View File

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

View File

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

View File

@@ -72,6 +72,24 @@ namespace Content.Shared.Damage
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>
/// Whether this damage specifier has any entries.
/// </summary>

View File

@@ -48,7 +48,7 @@ namespace Content.Shared.Damage
SubscribeLocalEvent<DamageableComponent, ComponentHandleState>(DamageableHandleState);
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
SubscribeLocalEvent<DamageableComponent, OnIrradiatedEvent>(OnIrradiated);
SubscribeLocalEvent<DamageableComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<DamageableComponent, RejuvenateEvent>(OnRejuvenate, after: [typeof(Content.Shared.StatusEffectNew.StatusEffectsSystem)]); // Offbrand
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
_damageableQuery = GetEntityQuery<DamageableComponent>();
@@ -145,7 +145,7 @@ namespace Content.Shared.Damage
/// The damage changed event is used by other systems, such as damage thresholds.
/// </remarks>
public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSpecifier? damageDelta = null,
bool interruptsDoAfters = true, EntityUid? origin = null)
bool interruptsDoAfters = true, EntityUid? origin = null, bool forcedRefresh = false) // Offbrand
{
component.Damage.GetDamagePerGroup(_prototypeManager, component.DamagePerGroup);
component.TotalDamage = component.Damage.GetTotal();
@@ -156,7 +156,7 @@ namespace Content.Shared.Damage
var data = new DamageVisualizerGroupData(component.DamagePerGroup.Keys.ToList());
_appearance.SetData(uid, DamageVisualizerKeys.DamageUpdateGroups, data, appearance);
}
RaiseLocalEvent(uid, new DamageChangedEvent(component, damageDelta, interruptsDoAfters, origin));
RaiseLocalEvent(uid, new DamageChangedEvent(component, damageDelta, interruptsDoAfters, origin, forcedRefresh)); // Offbrand
}
/// <summary>
@@ -172,7 +172,7 @@ namespace Content.Shared.Damage
/// null if the user had no applicable components that can take damage.
/// </returns>
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null)
bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null, bool forceRefresh = false) // Offbrand
{
if (!uid.HasValue || !_damageableQuery.Resolve(uid.Value, ref damageable, false))
{
@@ -180,7 +180,7 @@ namespace Content.Shared.Damage
return null;
}
if (damage.Empty)
if (damage.Empty && !forceRefresh) // Offbrand
{
return damage;
}
@@ -214,6 +214,12 @@ namespace Content.Shared.Damage
damage = ApplyUniversalAllModifiers(damage);
// Begin Offbrand
var beforeCommit = new Content.Shared._Offbrand.Wounds.BeforeDamageCommitEvent(damage);
RaiseLocalEvent(uid.Value, ref beforeCommit);
damage = beforeCommit.Damage;
// End Offbrand
// TODO DAMAGE PERFORMANCE
// Consider using a local private field instead of creating a new dictionary here.
// Would need to check that nothing ever tries to cache the delta.
@@ -236,7 +242,7 @@ namespace Content.Shared.Damage
}
if (delta.DamageDict.Count > 0)
DamageChanged(uid.Value, damageable, delta, interruptsDoAfters, origin);
DamageChanged(uid.Value, damageable, delta, interruptsDoAfters, origin, forceRefresh); // Offbrand
return delta;
}
@@ -334,6 +340,7 @@ namespace Content.Shared.Damage
private void OnRejuvenate(EntityUid uid, DamageableComponent component, RejuvenateEvent args)
{
Log.Debug("rejuvenate damage");
TryComp<MobThresholdsComponent>(uid, out var thresholds);
_mobThreshold.SetAllowRevives(uid, true, thresholds); // do this so that the state changes when we set the damage
SetAllDamage(uid, component, 0);
@@ -429,11 +436,18 @@ namespace Content.Shared.Damage
/// </summary>
public readonly EntityUid? Origin;
public DamageChangedEvent(DamageableComponent damageable, DamageSpecifier? damageDelta, bool interruptsDoAfters, EntityUid? origin)
// Offbrand
/// <summary>
/// If this damage changed happened as part of a forced refresh
/// </summary>
public readonly bool ForcedRefresh;
public DamageChangedEvent(DamageableComponent damageable, DamageSpecifier? damageDelta, bool interruptsDoAfters, EntityUid? origin, bool forcedRefresh) // Offbrand
{
Damageable = damageable;
DamageDelta = damageDelta;
Origin = origin;
ForcedRefresh = forcedRefresh; // Offbrand
if (DamageDelta == null)
return;

View File

@@ -46,6 +46,13 @@ namespace Content.Shared.FixedPoint
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 FromHundredths(int value) => new(value);

View File

@@ -201,13 +201,13 @@ namespace Content.Shared.FixedPoint
}
// 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(double n) => New(n);
public static implicit operator FixedPoint4(int 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 double(FixedPoint4 n) => n.Double();
public static explicit operator int(FixedPoint4 n) => n.Int();

View File

@@ -88,14 +88,16 @@ public sealed class HealthExaminableSystem : EntitySystem
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)
{
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;
}
}

View File

@@ -43,6 +43,9 @@ public sealed class IdentitySystem : EntitySystem
SubscribeLocalEvent<IdentityBlockerComponent, SeeIdentityAttemptEvent>(OnSeeIdentity);
SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<SeeIdentityAttemptEvent>>(OnRelaySeeIdentity);
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, ComponentInit>(OnComponentInit);
@@ -55,6 +58,15 @@ public sealed class IdentitySystem : EntitySystem
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>
/// Iterates through all identities that need to be updated.
/// </summary>

View File

@@ -26,6 +26,7 @@ using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Shared.Medical.Cryogenics;
@@ -105,7 +106,9 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
&& _solutionContainerQuery.TryComp(container, out var solutionContainerManagerComponent)
&& _solutionContainer.TryGetFitsInDispenser((container.Value, fitsInDispenserComponent, solutionContainerManagerComponent),
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);
_bloodstream.TryAddToChemicals((patient.Value, bloodstream), solutionToInject);

View File

@@ -1,6 +1,7 @@
using Content.Shared.DoAfter;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Shared.Medical.SuitSensor;
@@ -25,9 +26,12 @@ public sealed class SuitSensorStatus
public string JobIcon;
public List<string> JobDepartments;
public bool IsAlive;
public int? TotalDamage;
public int? TotalDamageThreshold;
public float? DamagePercentage => TotalDamageThreshold == null || TotalDamage == null ? null : TotalDamage / (float) TotalDamageThreshold;
// Begin Offbrand Changes
// public int? TotalDamage;
// public int? TotalDamageThreshold;
// public float? DamagePercentage => TotalDamageThreshold == null || TotalDamage == null ? null : TotalDamage / (float) TotalDamageThreshold;
public WoundableHealthAnalyzerData? WoundableData;
// End Offbrand Changes
public NetCoordinates? Coordinates;
}
@@ -63,10 +67,13 @@ public static class SuitSensorConstants
public const string NET_JOB_ICON = "jobIcon";
public const string NET_JOB_DEPARTMENTS = "jobDepartments";
public const string NET_IS_ALIVE = "alive";
public const string NET_TOTAL_DAMAGE = "vitals";
public const string NET_TOTAL_DAMAGE_THRESHOLD = "vitalsThreshold";
// Begin Offbrand Changes
// 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_WOUNDABLE_DATA = "woundableData";
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
public const string NET_STATUS_COLLECTION = "suit-status-collection";

View File

@@ -21,6 +21,9 @@ using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Content.Shared.FixedPoint; // Offbrand
using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Shared.Medical.SuitSensors;
@@ -30,7 +33,8 @@ public abstract class SharedSuitSensorSystem : EntitySystem
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = 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 SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
@@ -352,15 +356,17 @@ public abstract class SharedSuitSensorSystem : EntitySystem
if (TryComp(sensor.User.Value, out MobStateComponent? mobState))
isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState);
// get mob total damage
var totalDamage = 0;
if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
totalDamage = damageable.TotalDamage.Int();
// Begin Offbrand Removals
// // get mob total damage
// var totalDamage = 0;
// if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
// totalDamage = damageable.TotalDamage.Int();
// Get mob total damage crit threshold
int? totalDamageThreshold = null;
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
totalDamageThreshold = critThreshold.Value.Int();
// // Get mob total damage crit threshold
// int? totalDamageThreshold = null;
// if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
// totalDamageThreshold = critThreshold.Value.Int();
// End Offbrand Removals
// finally, form suit sensor status
var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments);
@@ -370,14 +376,19 @@ public abstract class SharedSuitSensorSystem : EntitySystem
status.IsAlive = isAlive;
break;
case SuitSensorMode.SensorVitals:
status.IsAlive = isAlive;
status.TotalDamage = totalDamage;
status.TotalDamageThreshold = totalDamageThreshold;
break;
// Begin Offbrand Changes
// status.IsAlive = isAlive;
// status.TotalDamage = totalDamage;
// status.TotalDamageThreshold = totalDamageThreshold;
status.WoundableData = _woundableHealthAnalyzer.TakeSample(sensor.User.Value, withWounds: false);
goto case SuitSensorMode.SensorBinary;
// End Offbrand Changes
case SuitSensorMode.SensorCords:
status.IsAlive = isAlive;
status.TotalDamage = totalDamage;
status.TotalDamageThreshold = totalDamageThreshold;
// Begin Offbrand - don't duplicate code
// status.TotalDamage = totalDamage;
// status.TotalDamageThreshold = totalDamageThreshold;
// End Offbrand - don't duplicate code
EntityCoordinates coordinates;
var xformQuery = GetEntityQuery<TransformComponent>();
@@ -398,7 +409,7 @@ public abstract class SharedSuitSensorSystem : EntitySystem
}
status.Coordinates = GetNetCoordinates(coordinates);
break;
goto case SuitSensorMode.SensorVitals; // Offbrand - don't duplicate code
}
return status;
@@ -421,10 +432,14 @@ public abstract class SharedSuitSensorSystem : EntitySystem
[SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid,
};
if (status.TotalDamage != null)
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
if (status.TotalDamageThreshold != null)
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold);
// Begin Offbrand Changes
// if (status.TotalDamage != null)
// payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
// 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)
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
@@ -452,15 +467,21 @@ public abstract class SharedSuitSensorSystem : EntitySystem
if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null;
// try get total damage and cords (optionals)
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold);
// Begin Offbrand Changes
// 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);
var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments)
{
IsAlive = isAlive.Value,
TotalDamage = totalDamage,
TotalDamageThreshold = totalDamageThreshold,
// Begin Offbrand Changes
// TotalDamage = totalDamage,
// TotalDamageThreshold = totalDamageThreshold,
WoundableData = woundableData,
// End Offbrand Changes
Coordinates = coords,
};
return status;

View File

@@ -1,3 +1,4 @@
using Content.Shared._Offbrand.Wounds; // Offbrand
using Robust.Shared.Serialization;
namespace Content.Shared.MedicalScanner;
@@ -14,8 +15,9 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage
public bool? ScanMode;
public bool? Bleeding;
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;
Temperature = temperature;
@@ -23,6 +25,7 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage
ScanMode = scanMode;
Bleeding = bleeding;
Unrevivable = unrevivable;
WoundableData = woundableData; // Offbrand
}
}

View File

@@ -18,6 +18,7 @@ public sealed class MobThresholdSystem : EntitySystem
{
SubscribeLocalEvent<MobThresholdsComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<MobThresholdsComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<MobThresholdsComponent, MapInitEvent>(MobThresholdMapInit); // Offbrand
SubscribeLocalEvent<MobThresholdsComponent, ComponentShutdown>(MobThresholdShutdown);
SubscribeLocalEvent<MobThresholdsComponent, ComponentStartup>(MobThresholdStartup);
@@ -439,6 +440,14 @@ public sealed class MobThresholdSystem : EntitySystem
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.PotentiallyUpdateDamageOverlay(ent);
RaiseLocalEvent(ent, ref overlayUpdate);
}
// End Offbrand
private void MobThresholdShutdown(EntityUid target, MobThresholdsComponent component, ComponentShutdown args)
{
if (component.TriggersAlerts)

View File

@@ -216,4 +216,10 @@ public sealed partial class RevenantComponent : Component
#endregion
[DataField] public EntityUid? Action;
/// <summary>
/// Offbrand - how much cold damage to deal on harvest
/// </summary>
[DataField]
public FixedPoint2 HarvestColdDamage = 200;
}

View File

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

View File

@@ -14,7 +14,7 @@ public sealed partial class StatusEffectsSystem
{
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, RejuvenateEvent>(RelayStatusEffectEvent);
// SubscribeLocalEvent<StatusEffectContainerComponent, RejuvenateEvent>(RelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, RefreshMovementSpeedModifiersEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, UpdateCanMoveEvent>(RelayStatusEffectEvent);
@@ -26,6 +26,33 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent<StatusEffectContainerComponent, StunEndAttemptEvent>(RefRelayStatusEffectEvent);
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.GetStrainEvent>(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.GetOxygenationModifier>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetStoppedCirculationModifier>(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.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.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

View File

@@ -33,7 +33,7 @@ public sealed partial class StatusEffectsSystem : EntitySystem
SubscribeLocalEvent<StatusEffectContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
SubscribeLocalEvent<StatusEffectContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
SubscribeLocalEvent<RejuvenateRemovedStatusEffectComponent, StatusEffectRelayedEvent<RejuvenateEvent>>(OnRejuvenate);
SubscribeLocalEvent<StatusEffectContainerComponent, RejuvenateEvent>(OnRejuvenate); // Offbrand
_containerQuery = GetEntityQuery<StatusEffectContainerComponent>();
_effectQuery = GetEntityQuery<StatusEffectComponent>();
@@ -114,11 +114,19 @@ public sealed partial class StatusEffectsSystem : EntitySystem
Dirty(args.Entity, statusComp);
}
private void OnRejuvenate(Entity<RejuvenateRemovedStatusEffectComponent> ent,
ref StatusEffectRelayedEvent<RejuvenateEvent> args)
// Begin Offbrand Changes
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>
/// 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.
/// Raised on both the tool and then target.
/// </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 float Fuel = fuel;
public EntityUid? Target { get; } = target; // Offbrand
}
/// <summary>

View File

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

View File

@@ -235,6 +235,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeDamageEvent(uid, new(component.Damage * Damageable.UniversalMeleeDamageModifier), new(), user, component.ResistanceBypass);
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);
}
@@ -246,6 +252,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeAttackRateEvent(uid, component.AttackRate, 1, user);
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;
}
@@ -268,6 +280,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeDamageEvent(uid, new(component.Damage * Damageable.UniversalMeleeDamageModifier), new(), user, component.ResistanceBypass);
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;
}

View File

@@ -547,6 +547,15 @@ public abstract partial class SharedGunSystem : EntitySystem
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)
{
comp.SoundGunshotModified = ev.SoundGunshot;

View File

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

View File

@@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.Buckle;
[RegisterComponent, NetworkedComponent]
[Access(typeof(StatusEffectOnStrapSystem))]
public sealed partial class StatusEffectOnStrapComponent : Component
{
[DataField(required: true)]
public EntProtoId StatusEffect;
}

View File

@@ -0,0 +1,61 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.FixedPoint;
namespace Content.Shared._Offbrand.Chemistry;
[ByRefEvent]
public struct BeforeInjectOnEventEvent
{
public FixedPoint2 InjectionAmount;
}

View File

@@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class AdjustReagentGaussian : EntityEffect
{
[DataField(required: true)]
public ProtoId<ReagentPrototype> Reagent;
[DataField(required: true)]
public double μ;
[DataField(required: true)]
public double σ;
public override void Effect(EntityEffectBaseArgs args)
{
if (args is not EntityEffectReagentArgs reagentArgs)
throw new NotImplementedException();
if (reagentArgs.Source == null)
return;
var rand = IoCManager.Resolve<IRobustRandom>();
var amount = rand.NextGaussian(μ, σ);
amount *= reagentArgs.Scale.Double();
if (amount < 0 && reagentArgs.Source.ContainsPrototype(Reagent))
reagentArgs.Source.RemoveReagent(Reagent, -amount);
else if (amount > 0)
reagentArgs.Source.AddReagent(Reagent, amount);
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var proto = prototype.Index(Reagent);
return Loc.GetString("reagent-effect-guidebook-adjust-reagent-gaussian",
("chance", Probability),
("deltasign", Math.Sign(μ)),
("reagent", proto.LocalizedName),
("mu", Math.Abs(μ)),
("sigma", Math.Abs(σ)));
}
}

View File

@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class BrainDamage : EntityEffectCondition
{
[DataField]
public FixedPoint2 Max = FixedPoint2.MaxValue;
[DataField]
public FixedPoint2 Min = FixedPoint2.Zero;
public override bool Condition(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<BrainDamageComponent>(args.TargetEntity, out var brain))
{
return brain.Damage >= Min && brain.Damage <= Max;
}
return false;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-brain-damage",
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
}

View File

@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class HeartDamage : EntityEffectCondition
{
[DataField]
public FixedPoint2 Max = FixedPoint2.MaxValue;
[DataField]
public FixedPoint2 Min = FixedPoint2.Zero;
public override bool Condition(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<HeartrateComponent>(args.TargetEntity, out var heartrate))
{
return heartrate.Damage >= Min && heartrate.Damage <= Max;
}
return false;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-heart-damage",
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
}

View File

@@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.EntityEffects;
using Content.Shared.Zombies;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class IsZombieImmune : EntityEffectCondition
{
[DataField]
public bool Invert = false;
public override bool Condition(EntityEffectBaseArgs args)
{
return args.EntityManager.HasComponent<ZombieImmuneComponent>(args.TargetEntity) ^ Invert;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-is-zombie-immune", ("invert", Invert));
}
}

View File

@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
using Content.Shared.EntityEffects;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class MetaboliteThreshold : EventEntityEffectCondition<MetaboliteThreshold>
{
[DataField]
public FixedPoint2 Min = FixedPoint2.Zero;
[DataField]
public FixedPoint2 Max = FixedPoint2.MaxValue;
[DataField]
public ProtoId<ReagentPrototype>? Reagent;
[DataField]
public bool IncludeBloodstream = true;
public override string GuidebookExplanation(IPrototypeManager prototype)
{
ReagentPrototype? reagentProto = null;
if (Reagent is { } reagent)
prototype.TryIndex(reagent, out reagentProto);
if (IncludeBloodstream)
{
return Loc.GetString("reagent-effect-condition-guidebook-total-dosage-threshold",
("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")),
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
else
{
return Loc.GetString("reagent-effect-condition-guidebook-metabolite-threshold",
("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-metabolite")),
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
("min", Min.Float()));
}
}
}

View File

@@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class ModifyBrainDamage : EntityEffect
{
[DataField(required: true)]
public FixedPoint2 Amount;
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (Amount < FixedPoint2.Zero)
return Loc.GetString("reagent-effect-guidebook-modify-brain-damage-heals", ("chance", Probability), ("amount", -Amount));
else
return Loc.GetString("reagent-effect-guidebook-modify-brain-damage-deals", ("chance", Probability), ("amount", Amount));
}
public override void Effect(EntityEffectBaseArgs args)
{
var scale = FixedPoint2.New(1);
if (args is EntityEffectReagentArgs reagentArgs)
scale = reagentArgs.Scale;
args.EntityManager.System<BrainDamageSystem>()
.TryChangeBrainDamage(args.TargetEntity, Amount * scale);
}
}

View File

@@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class ModifyBrainOxygen : EntityEffect
{
[DataField(required: true)]
public FixedPoint2 Amount;
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (Amount > FixedPoint2.Zero)
return Loc.GetString("reagent-effect-guidebook-modify-brain-oxygen-heals", ("chance", Probability), ("amount", Amount));
else
return Loc.GetString("reagent-effect-guidebook-modify-brain-oxygen-deals", ("chance", Probability), ("amount", -Amount));
}
public override void Effect(EntityEffectBaseArgs args)
{
var scale = FixedPoint2.New(1);
if (args is EntityEffectReagentArgs reagentArgs)
scale = reagentArgs.Scale;
args.EntityManager.System<BrainDamageSystem>()
.TryChangeBrainOxygenation(args.TargetEntity, Amount * scale);
}
}

View File

@@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class ModifyHeartDamage : EntityEffect
{
[DataField(required: true)]
public FixedPoint2 Amount;
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if (Amount < FixedPoint2.Zero)
return Loc.GetString("reagent-effect-guidebook-modify-heart-damage-heals", ("chance", Probability), ("amount", -Amount));
else
return Loc.GetString("reagent-effect-guidebook-modify-heart-damage-deals", ("chance", Probability), ("amount", Amount));
}
public override void Effect(EntityEffectBaseArgs args)
{
var scale = FixedPoint2.New(1);
if (args is EntityEffectReagentArgs reagentArgs)
scale = reagentArgs.Scale;
args.EntityManager.System<HeartSystem>()
.ChangeHeartDamage(args.TargetEntity, Amount * scale);
}
}

View File

@@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class StartHeart : EntityEffect
{
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return Loc.GetString("reagent-effect-guidebook-start-heart", ("chance", Probability));
}
public override void Effect(EntityEffectBaseArgs args)
{
args.EntityManager.System<HeartSystem>()
.TryRestartHeart(args.TargetEntity);
}
}

View File

@@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Shared._Offbrand.EntityEffects;
public sealed partial class Zombify : EventEntityEffect<Zombify>
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-zombify", ("chance", Probability));
}

View File

@@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization;
namespace Content.Shared._Offbrand.IV;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(IVSystem))]
public sealed partial class IVSourceComponent : Component
{
[DataField, AutoNetworkedField]
public EntityUid? IVTarget;
[DataField]
public string SlotName = "iv_bag_slot";
[DataField]
public FixedPoint2 BloodTransferRate = 5;
[DataField]
public FixedPoint2 OtherTransferRate = 0.5;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
public TimeSpan NextUpdate = TimeSpan.Zero;
[DataField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
[DataField]
public float Delay = 3f;
[DataField]
public LocId NoBagInserted = "iv-bag-none-inserted";
[DataField]
public LocId StartConnectionUser = "iv-bag-start-connection-user";
[DataField]
public LocId StartConnectionOthers = "iv-bag-start-connection-others";
[DataField]
public LocId ConnectedUser = "iv-bag-connected-user";
[DataField]
public LocId ConnectedOthers = "iv-bag-connected-others";
[DataField]
public LocId StartDisconnectionUser = "iv-bag-start-disconnection-user";
[DataField]
public LocId StartDisconnectionOthers = "iv-bag-start-disconnection-others";
[DataField]
public LocId DisconnectedUser = "iv-bag-disconnected-user";
[DataField]
public LocId DisconnectedOthers = "iv-bag-disconnected-others";
}
[Serializable, NetSerializable]
public sealed partial class IVConnectDoAfterEvent : SimpleDoAfterEvent;
[Serializable, NetSerializable]
public sealed partial class IVDisconnectDoAfterEvent : SimpleDoAfterEvent;
[Serializable, NetSerializable]
public enum IVSourceVisuals : byte
{
HasTarget
}

View File

@@ -0,0 +1,327 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.GameStates;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Content.Shared._Offbrand.IV;
public sealed class IVSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedJointSystem _joint = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<IVSourceComponent, CanDragEvent>(OnCanDrag);
SubscribeLocalEvent<IVSourceComponent, CanDropDraggedEvent>(OnCanDropDragged);
SubscribeLocalEvent<IVSourceComponent, DragDropDraggedEvent>(OnDragDropDragged);
SubscribeLocalEvent<IVSourceComponent, IVConnectDoAfterEvent>(OnConnectDoAfter);
SubscribeLocalEvent<IVSourceComponent, IVDisconnectDoAfterEvent>(OnDisconnectDoAfter);
SubscribeLocalEvent<IVSourceComponent, GetVerbsEvent<Verb>>(OnSourceGetVerbs);
SubscribeLocalEvent<IVTargetComponent, GetVerbsEvent<Verb>>(OnTargetGetVerbs);
SubscribeLocalEvent<IVSourceComponent, ComponentShutdown>(OnSourceShutdown);
SubscribeLocalEvent<IVTargetComponent, ComponentShutdown>(OnTargetShutdown);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<IVSourceComponent>();
while (query.MoveNext(out var sourceUid, out var sourceComp))
{
if (sourceComp.IVTarget is not { } target)
continue;
if (sourceComp.NextUpdate > _timing.CurTime)
continue;
sourceComp.NextUpdate = _timing.CurTime + sourceComp.UpdateInterval;
Dirty(sourceUid, sourceComp);
if (!TryComp<BloodstreamComponent>(target, out var bloodstream) || !TryComp<IVTargetComponent>(target, out var targetComp))
continue;
TickIV((sourceUid, sourceComp), (target, targetComp, bloodstream));
}
}
private void OnCanDrag(Entity<IVSourceComponent> ent, ref CanDragEvent args)
{
args.Handled = ent.Comp.IVTarget is null;
}
private void OnCanDropDragged(Entity<IVSourceComponent> ent, ref CanDropDraggedEvent args)
{
if (!TryComp<IVTargetComponent>(args.Target, out var target))
return;
args.Handled = true;
args.CanDrop = target.IVSource is null;
}
private void OnDragDropDragged(Entity<IVSourceComponent> ent, ref DragDropDraggedEvent args)
{
if (!TryComp<IVTargetComponent>(args.Target, out var target) || target.IVSource is not null)
return;
args.Handled = true;
TryStartIV(ent.AsNullable(), (args.Target, target), args.User);
}
private void OnConnectDoAfter(Entity<IVSourceComponent> ent, ref IVConnectDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target is not { } target)
return;
StartIV(ent.AsNullable(), target, args.Args.User);
}
private void OnDisconnectDoAfter(Entity<IVSourceComponent> ent, ref IVDisconnectDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target is not { } target)
return;
StopIV(ent.AsNullable(), target, args.Args.User);
}
private void TickIV(Entity<IVSourceComponent> source, Entity<IVTargetComponent, BloodstreamComponent> target)
{
if (_itemSlots.GetItemOrNull(source, source.Comp.SlotName) is not { } contained)
return;
if (!_solutionContainer.TryGetDrawableSolution(contained, out var solutionEntity, out var solution))
return;
if (!_solutionContainer.ResolveSolution(target.Owner, target.Comp2.ChemicalSolutionName, ref target.Comp2.ChemicalSolution, out var chemsSolution))
return;
if (!_solutionContainer.ResolveSolution(target.Owner, target.Comp2.BloodSolutionName, ref target.Comp2.BloodSolution, out var bloodSolution))
return;
var bloodTransferAmount = FixedPoint2.Min(source.Comp.BloodTransferRate, bloodSolution.AvailableVolume);
var chemsTransferAmount = FixedPoint2.Min(source.Comp.OtherTransferRate, chemsSolution.AvailableVolume);
if (bloodTransferAmount > 0)
{
solution.SplitSolutionWithOnly(bloodTransferAmount, target.Comp2.BloodReagent);
_bloodstream.TryModifyBloodLevel((target.Owner, target.Comp2), bloodTransferAmount);
}
if (chemsTransferAmount > 0)
{
var medicineSolution = solution.SplitSolutionWithout(chemsTransferAmount, target.Comp2.BloodReagent);
if (!chemsSolution.HasOverlapAtLeast(medicineSolution, source.Comp.OtherTransferRate * 2))
{
_bloodstream.TryAddToChemicals((target.Owner, target.Comp2), medicineSolution);
}
}
_solutionContainer.UpdateChemicals(solutionEntity.Value);
}
private void SetLock(Entity<IVSourceComponent> ent, bool locked)
{
_itemSlots.SetLock(ent, ent.Comp.SlotName, locked);
_appearance.SetData(ent.Owner, IVSourceVisuals.HasTarget, locked);
}
private void StartIV(Entity<IVSourceComponent?> source, Entity<IVTargetComponent?> target, EntityUid user)
{
if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp))
return;
if (!TryComp<PhysicsComponent>(source, out var sourcePhysics))
return;
if (!TryComp<PhysicsComponent>(target, out var targetPhysics))
return;
_popup.PopupPredicted(
Loc.GetString(source.Comp.ConnectedUser, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
Loc.GetString(source.Comp.ConnectedOthers, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
target,
user
);
target.Comp.IVSource = source;
source.Comp.IVTarget = target;
target.Comp.IVJointID = $"iv-joint-{GetNetEntity(target)}";
if (!_timing.ApplyingState)
{
var joint = _joint.CreateDistanceJoint(target, source,
sourcePhysics.LocalCenter, targetPhysics.LocalCenter,
id: target.Comp.IVJointID);
joint.MaxLength = joint.Length + 0.2f;
joint.MinLength = 0f;
joint.Stiffness = 0f;
}
SetLock((source, source.Comp), true);
Dirty(target);
Dirty(source);
}
private void StopIV(Entity<IVSourceComponent?> source, Entity<IVTargetComponent?> target, EntityUid user)
{
if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp))
return;
_popup.PopupPredicted(
Loc.GetString(source.Comp.DisconnectedUser, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
Loc.GetString(source.Comp.DisconnectedOthers, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
target,
user
);
source.Comp.IVTarget = null;
if (target.Comp.IVJointID is { } joint)
_joint.RemoveJoint(target, joint);
target.Comp.IVSource = null;
target.Comp.IVJointID = null;
SetLock((source, source.Comp), false);
Dirty(source);
Dirty(target);
}
private void TryStartIV(Entity<IVSourceComponent?> source, Entity<IVTargetComponent?> target, EntityUid user)
{
if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp) || source.Comp.IVTarget is not null || target.Comp.IVSource is not null)
return;
if (_itemSlots.GetItemOrNull(source, source.Comp.SlotName) is not { } contained)
{
_popup.PopupPredictedCursor(Loc.GetString(source.Comp.NoBagInserted), user);
return;
}
_popup.PopupPredicted(
Loc.GetString(source.Comp.StartConnectionUser, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
Loc.GetString(source.Comp.StartConnectionOthers, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
target,
user
);
var args =
new DoAfterArgs(EntityManager, user, source.Comp.Delay, new IVConnectDoAfterEvent(), source, target: target)
{
NeedHand = true,
BreakOnMove = true,
BreakOnWeightlessMove = false,
};
_doAfter.TryStartDoAfter(args);
}
private void TryStopIV(Entity<IVSourceComponent?> source, Entity<IVTargetComponent?> target, EntityUid user)
{
if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp) || source.Comp.IVTarget is null || target.Comp.IVSource is null || source.Comp.IVTarget != target || target.Comp.IVSource != source)
return;
_popup.PopupPredicted(
Loc.GetString(source.Comp.StartDisconnectionUser, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
Loc.GetString(source.Comp.StartDisconnectionOthers, ("target", Identity.Entity(target, EntityManager)), ("source", Identity.Entity(source, EntityManager)), ("user", Identity.Entity(user, EntityManager))),
target,
user
);
var args =
new DoAfterArgs(EntityManager, user, source.Comp.Delay, new IVDisconnectDoAfterEvent(), source, target: target)
{
NeedHand = true,
BreakOnMove = true,
BreakOnWeightlessMove = false,
};
_doAfter.TryStartDoAfter(args);
}
private void OnSourceShutdown(Entity<IVSourceComponent> ent, ref ComponentShutdown args)
{
if (ent.Comp.IVTarget is not { } target || !TryComp<IVTargetComponent>(target, out var targetComp))
return;
if (targetComp.IVJointID is { } joint)
_joint.RemoveJoint(target, joint);
targetComp.IVSource = null;
targetComp.IVJointID = null;
SetLock(ent, false);
Dirty(target, targetComp);
}
private void OnTargetShutdown(Entity<IVTargetComponent> ent, ref ComponentShutdown args)
{
if (ent.Comp.IVSource is not { } source || !TryComp<IVSourceComponent>(source, out var sourceComp))
return;
if (ent.Comp.IVJointID is { } joint)
_joint.RemoveJoint(ent, joint);
sourceComp.IVTarget = null;
SetLock((source, sourceComp), false);
Dirty(source, sourceComp);
}
private void OnSourceGetVerbs(Entity<IVSourceComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || ent.Comp.IVTarget is not { } target)
return;
var user = args.User;
Verb verb = new()
{
Text = Loc.GetString("verb-remove-iv"),
Act = () => TryStopIV(ent.AsNullable(), target, user)
};
args.Verbs.Add(verb);
}
private void OnTargetGetVerbs(Entity<IVTargetComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || ent.Comp.IVSource is not { } source)
return;
var user = args.User;
Verb verb = new()
{
Text = Loc.GetString("verb-remove-iv"),
Act = () => TryStopIV(source, ent.AsNullable(), user)
};
args.Verbs.Add(verb);
}
}

View File

@@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Robust.Shared.GameStates;
namespace Content.Shared._Offbrand.IV;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(IVSystem))]
public sealed partial class IVTargetComponent : Component
{
[DataField, AutoNetworkedField]
public EntityUid? IVSource;
[DataField, AutoNetworkedField]
public string? IVJointID;
}

View File

@@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.DoAfter;
using Content.Shared.Eui;
using Content.Shared.Whitelist;
using Robust.Shared.Serialization;
namespace Content.Shared._Offbrand.MMI;
[RegisterComponent]
public sealed partial class MMIExtractorComponent : Component
{
[DataField]
public float Delay = 30f;
[DataField]
public EntityWhitelist? Whitelist;
[DataField]
public EntityWhitelist? Blacklist;
[DataField]
public LocId NoMind = "mmi-extractor-no-mind";
[DataField]
public LocId Asking = "mmi-extractor-probing";
[DataField]
public LocId Accepted = "mmi-extractor-accepted";
[DataField]
public LocId Denied = "mmi-extractor-denied";
[DataField]
public LocId NoResponse = "mmi-extractor-inconclusive";
[DataField]
public LocId TooManyBrains = "mmi-extractor-too-many-brains";
[DataField]
public LocId Brainless = "mmi-extractor-brainless";
}
[Serializable, NetSerializable]
public sealed class MMIExtractorMessage : EuiMessageBase
{
public readonly bool Accepted;
public MMIExtractorMessage(bool accepted)
{
Accepted = accepted;
}
}
[Serializable, NetSerializable]
public sealed partial class MMIExtractorDoAfterEvent : SimpleDoAfterEvent
{
[DataField]
public bool Accepted = false;
}

View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.Whitelist;
using Robust.Shared.Serialization;
namespace Content.Shared._Offbrand.SolutionAppearanceRelay;
[RegisterComponent]
public sealed partial class SolutionAppearanceRelayComponent : Component
{
[DataField(required: true)]
public string Solution;
[DataField]
public EntityWhitelist? Whitelist;
[DataField]
public EntityWhitelist? Blacklist;
}
[Serializable, NetSerializable]
public enum SolutionAppearanceRelayedVisuals : byte
{
HasRelay
}

View File

@@ -0,0 +1,67 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
namespace Content.Shared._Offbrand.SolutionAppearanceRelay;
public sealed class SolutionAppearanceRelaySystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionAppearanceRelayComponent, SolutionContainerChangedEvent>(OnSolutionContainerChanged);
SubscribeLocalEvent<SolutionAppearanceRelayComponent, EntGotInsertedIntoContainerMessage>(OnEntGotInsertedIntoContainer);
SubscribeLocalEvent<SolutionAppearanceRelayComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
}
private void OnSolutionContainerChanged(Entity<SolutionAppearanceRelayComponent> ent, ref SolutionContainerChangedEvent args)
{
UpdateAppearance(ent);
}
private void OnEntGotInsertedIntoContainer(Entity<SolutionAppearanceRelayComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
UpdateAppearance(ent);
}
private void OnEntGotRemovedFromContainer(Entity<SolutionAppearanceRelayComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
if (!_entityWhitelist.CheckBoth(args.Container.Owner, ent.Comp.Blacklist, ent.Comp.Whitelist))
return;
_appearance.SetData(args.Container.Owner, SolutionContainerVisuals.FillFraction, 0);
_appearance.SetData(args.Container.Owner, SolutionAppearanceRelayedVisuals.HasRelay, false);
}
private void UpdateAppearance(Entity<SolutionAppearanceRelayComponent> ent)
{
if (!_container.TryGetContainingContainer((ent, null, null), out var container))
return;
if (!_solutionContainer.TryGetSolution(ent.Owner, ent.Comp.Solution, out var solutionEntity, out _))
return;
if (!TryComp<ContainedSolutionComponent>(solutionEntity, out var containedSolution))
return;
if (!_entityWhitelist.CheckBoth(container.Owner, ent.Comp.Blacklist, ent.Comp.Whitelist))
return;
_solutionContainer.UpdateAppearance(container.Owner, (solutionEntity.Value.Owner, solutionEntity.Value.Comp, containedSolution));
_appearance.SetData(container.Owner, SolutionAppearanceRelayedVisuals.HasRelay, true);
}
}

View File

@@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared.FixedPoint;
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(AssistedCirculationStatusEffectSystem))]
public sealed partial class AssistedCirculationStatusEffectComponent : Component
{
/// <summary>
/// How much blood circulation to add
/// </summary>
[DataField(required: true)]
public FixedPoint2 Amount;
}

View File

@@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.FixedPoint;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed partial class AssistedCirculationStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AssistedCirculationStatusEffectComponent, StatusEffectRelayedEvent<GetStoppedCirculationModifier>>(OnGetStoppedCirculationModifier);
}
private void OnGetStoppedCirculationModifier(Entity<AssistedCirculationStatusEffectComponent> ent, ref StatusEffectRelayedEvent<GetStoppedCirculationModifier> args)
{
args.Args = args.Args with { Modifier = FixedPoint2.Clamp(args.Args.Modifier + ent.Comp.Amount, 0, 1) };
}
}

View File

@@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(BleedMultiplierStatusEffectSystem))]
public sealed partial class BleedMultiplierStatusEffectComponent : Component
{
[DataField(required: true)]
public float Multiplier;
}

View File

@@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class BleedMultiplierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BleedMultiplierStatusEffectComponent, StatusEffectRelayedEvent<ModifyBleedLevelEvent>>(OnGetBleedMultiplier);
}
private void OnGetBleedMultiplier(Entity<BleedMultiplierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<ModifyBleedLevelEvent> args)
{
args.Args = args.Args with { BleedLevel = args.Args.BleedLevel * ent.Comp.Multiplier };
}
}

View File

@@ -0,0 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(BlindnessStatusEffectSystem))]
public sealed partial class BlindnessStatusEffectComponent : Component;

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