Skip to content

Commit

Permalink
CTV: Medical (ekrixi-14#217)
Browse files Browse the repository at this point in the history
* Basic wounds functionality

* Wounds examination

* remove height bar

* remove this too

* Treatment code

* Wounds work all fine and dandy

* Tool quality checker

* Singleton wounds

* Change tools

* Split treatment paths from struct to class

* Wound thresholds and damage specifiers

* Buff surgical tools

* Wounds are done

* Buff VIE

* fix tests??
  • Loading branch information
Just-a-Unity-Dev committed Mar 28, 2024
1 parent 324069a commit 7b59a3d
Show file tree
Hide file tree
Showing 20 changed files with 845 additions and 49 deletions.
12 changes: 6 additions & 6 deletions Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@
<OptionButton Name="CSpeciesButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Height -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-height-label'}" />
<Label Name="CHeightLabel" Text="1" />
<Button Name="CHeightReset" Text="{Loc 'humanoid-profile-editor-reset-height-button'}"/>
<LineEdit HorizontalAlignment="Right" HorizontalExpand="True" Name="CHeight" MinSize="40 0" Text="1.0" />
</BoxContainer>
<!-- <BoxContainer HorizontalExpand="True"> -->
<!-- <Label Text="{Loc 'humanoid-profile-editor-height-label'}" /> -->
<!-- <Label Name="CHeightLabel" Text="1" /> -->
<!-- <Button Name="CHeightReset" Text="{Loc 'humanoid-profile-editor-reset-height-button'}"/> -->
<!-- <LineEdit HorizontalAlignment="Right" HorizontalExpand="True" Name="CHeight" MinSize="40 0" Text="1.0" /> -->
<!-- </BoxContainer> -->
</BoxContainer>
<!-- Skin -->
<BoxContainer Margin="10" HorizontalExpand="True" Orientation="Vertical">
Expand Down
26 changes: 13 additions & 13 deletions Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,19 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt

#region Height

CHeight.OnTextChanged += args =>
{
if (!float.TryParse(args.Text, out var newHeight))
return;
CHeightLabel.Text = MathF.Round(newHeight, 1).ToString("G");
SetHeight(newHeight);
};

CHeightReset.OnPressed += _ =>
{
CHeight.Text = _defaultHeight.ToString(CultureInfo.InvariantCulture);
};
// CHeight.OnTextChanged += args =>
// {
// if (!float.TryParse(args.Text, out var newHeight))
// return;
//
// CHeightLabel.Text = MathF.Round(newHeight, 1).ToString("G");
// SetHeight(newHeight);
// };
//
// CHeightReset.OnPressed += _ =>
// {
// CHeight.Text = _defaultHeight.ToString(CultureInfo.InvariantCulture);
// };

#endregion Height

Expand Down
28 changes: 23 additions & 5 deletions Content.Server/HealthExaminable/HealthExaminableSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Shared.Damage;
using Content.Shared._FTL.Wounds;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
Expand All @@ -22,14 +23,15 @@ private void OnGetExamineVerbs(EntityUid uid, HealthExaminableComponent componen
{
if (!TryComp<DamageableComponent>(uid, out var damage))
return;
TryComp<WoundsHolderComponent>(uid, out var wounds);

var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid);
var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid);

var verb = new ExamineVerb()
{
Act = () =>
{
var markup = CreateMarkup(uid, component, damage);
var markup = CreateMarkup(uid, component, damage, wounds);
_examineSystem.SendExamineTooltip(args.User, uid, markup, false, false);
},
Text = Loc.GetString("health-examinable-verb-text"),
Expand All @@ -42,7 +44,7 @@ private void OnGetExamineVerbs(EntityUid uid, HealthExaminableComponent componen
args.Verbs.Add(verb);
}

private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent component, DamageableComponent damage)
private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent component, DamageableComponent damage, WoundsHolderComponent? wounds)
{
var msg = new FormattedMessage();

Expand All @@ -57,7 +59,7 @@ private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent c

FixedPoint2 closest = FixedPoint2.Zero;

string chosenLocStr = string.Empty;
var chosenLocStr = string.Empty;
foreach (var threshold in component.Thresholds)
{
var str = $"health-examinable-{component.LocPrefix}-{type}-{threshold}";
Expand Down Expand Up @@ -88,6 +90,22 @@ private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent c
msg.AddMarkup(chosenLocStr);
}

if (wounds != null)
{
foreach (var wound in wounds.Wounds.ContainedEntities)
{
if (!TryComp<WoundComponent>(wound, out var wComp))
continue;

if (!first)
msg.PushNewline();
else
first = false;

msg.AddMarkup(Loc.GetString(wComp.WoundExamineMessage, ("target", Identity.Entity(uid, EntityManager))));
}
}

if (msg.IsEmpty)
{
msg.AddMarkup(Loc.GetString($"health-examinable-{component.LocPrefix}-none"));
Expand Down
19 changes: 19 additions & 0 deletions Content.Server/_FTL/Wounds/WoundThresholdComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Content.Server._FTL.Wounds;

[RegisterComponent]
public sealed partial class WoundThresholdComponent : Component
{
[DataField("thresholds")]
public List<WoundThreshold> Thresholds = new();

public float TimeSinceLastUpdate = 0;
}

[DataDefinition]
public partial record struct WoundThreshold
{
[DataField, ViewVariables] public string Wound;
[DataField, ViewVariables] public string DamageType;
[DataField, ViewVariables] public float Probability;
[DataField, ViewVariables] public float Threshold;
}
223 changes: 223 additions & 0 deletions Content.Server/_FTL/Wounds/WoundTreatmentSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Tools;
using Content.Shared._FTL.Wounds;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Popups;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;

namespace Content.Server._FTL.Wounds;

/// <summary>
/// This handles the treating of wounds.
/// </summary>
/// <remarks>
/// This is a shitty work around because putting this code in shared doesn't let me delete the entities.
/// </remarks>
public sealed class WoundTreatmentSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly SharedWoundsSystem _woundsSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;

/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<WoundsHolderComponent, GetVerbsEvent<AlternativeVerb>>(AddTreatVerb);
SubscribeLocalEvent<WoundsHolderComponent, WoundTreatmentDoAfterEvent>(OnDoAfter);
}

private void OnDoAfter(EntityUid uid, WoundsHolderComponent component, WoundTreatmentDoAfterEvent args)
{
if (args.Cancelled)
return;

var currentWoundEntity = _entMan.GetEntity(args.Entity);
var user = args.Args.User;
var woundHolder = _entMan.GetEntity(args.WoundHolder);

if (args.Handled)
return;

if (!TryComp<DamageableComponent>(woundHolder, out var damageable))
return;

var currentWound = EnsureComp<WoundComponent>(currentWoundEntity);

// If the current treatment path is more than the treatment paths available
// We know the last treatment path is performed, so remove the wound
if (currentWound.CurrentTreatmentPath < currentWound.TreatmentPaths.Count)
{
var currentPath = currentWound.TreatmentPaths[currentWound.CurrentTreatmentPath];
if ((currentWound.CurrentTreatmentPath - 1) > 0)
{
var prevPath = currentWound.TreatmentPaths[currentWound.CurrentTreatmentPath - 1];
prevPath.OnTreatmentEnd(_entMan);
}

var endedMsgUser = Loc.GetString(currentPath.EndedMessage);
var endedMsgOther = Loc.GetString(currentPath.EndedMessage + "-other", ("target", woundHolder));

// Actually increment it here since we're finished
currentWound.CurrentTreatmentPath += 1;
Dirty(currentWoundEntity, currentWound);

if (currentWound.CurrentTreatmentPath < currentWound.TreatmentPaths.Count)
{
_popupSystem.PopupEntity(endedMsgUser, woundHolder, user);
_popupSystem.PopupEntity(endedMsgOther, woundHolder, Filter.PvsExcept(user), true);
return;
}
}

_popupSystem.PopupEntity(Loc.GetString("popup-wound-cured", ("target", uid), ("woundName", MetaData(currentWoundEntity).EntityName)), uid);
QueueDel(currentWoundEntity);
_damageableSystem.DamageChanged(uid, damageable);
}

public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<WoundThresholdComponent, WoundsHolderComponent, DamageableComponent>();
while (query.MoveNext(out var entity, out var thresholdComponent, out var woundsHolderComponent, out var damageableComponent))
{
thresholdComponent.TimeSinceLastUpdate += frameTime;

if (thresholdComponent.TimeSinceLastUpdate < 5)
continue; // run this every 5 seconds

thresholdComponent.TimeSinceLastUpdate = 0;

foreach (var threshold in thresholdComponent.Thresholds)
{
if (!damageableComponent.Damage.DamageDict.TryGetValue(threshold.DamageType, out var damageAmount))
continue;

if (damageAmount < threshold.Threshold)
continue;

if (!_random.Prob(threshold.Probability))
continue;

_woundsSystem.TryAddWound(threshold.Wound, entity, woundsHolderComponent);
}
}
}

private void AddTreatVerb(EntityUid uid, WoundsHolderComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract)
return;

if (component.Wounds.ContainedEntities.Count <= 0)
return; // why show a treat menu when theres nothing to treat

for (var i = 0; i < component.Wounds.ContainedEntities.Count; i++)
{
var wound = component.Wounds.ContainedEntities[i];

if (!TryComp<WoundComponent>(wound, out _))
return;

var meta = MetaData(wound);

var i1 = i;
args.Verbs.Add(new AlternativeVerb
{
Text = $"{meta.EntityName}",
Act = () =>
{
component.CurrentWoundTreating = i1;
},
Disabled = i == component.CurrentWoundTreating,
Category = VerbCategory.SelectWound
});
}

// Hamlet? From Don't Starve Hamlet (DLC?)
// gardeners are RUINING ss14
// i do not want to hear it... EVER
// -flare

// Get the currently selected wound
var currentWoundEntity = component.Wounds.ContainedEntities[component.CurrentWoundTreating];
if (!TryComp<WoundComponent>(currentWoundEntity, out var currentWound))
return; // If it doesnt have a wound comp wtf is it doing here???

// Get the current treatment path
var currentPath = currentWound.TreatmentPaths[currentWound.CurrentTreatmentPath];

if (args.Hands == null)
{
_popupSystem.PopupClient(Loc.GetString("popup-wound-need-hand"), uid, args.User);
return; // you need hands
}

if (args.Hands.ActiveHand == null)
{
_popupSystem.PopupClient(Loc.GetString("popup-wound-need-hand"), uid, args.User);
return; // you need A hand at least
}

var quality = _prototypeManager.Index<ToolQualityPrototype>(currentPath.ToolQuality.Id);

args.Verbs.Add(new AlternativeVerb
{
Text = currentPath.GetVerbText(currentWound),
Act = () =>
{
var currentlyHeld = args.Hands?.ActiveHand?.HeldEntity;
if (!currentlyHeld.HasValue)
return;
if (!currentPath.TreatmentCheck(_entMan, _entMan.GetNetEntity(currentlyHeld.Value)))
{
_popupSystem.PopupEntity(Loc.GetString("popup-wound-need-item", ("item", Loc.GetString(quality.Name))), uid);
return;
}
var tool = EnsureComp<ToolComponent>(currentlyHeld.Value);
_audioSystem.PlayPvs(currentWound.TreatmentPaths[currentWound.CurrentTreatmentPath].TreatmentSound, uid);
var startedMsgUser = Loc.GetString(currentPath.BeganMessage);
var startedMsgOther = Loc.GetString(currentPath.BeganMessage + "-other", ("target", args.User));
_popupSystem.PopupEntity(startedMsgUser, uid, args.User);
_popupSystem.PopupEntity(startedMsgOther, uid, Filter.PvsExcept(args.User), true);
var ev = new WoundTreatmentDoAfterEvent(
_entMan.GetNetEntity(currentWoundEntity),
_entMan.GetNetEntity(uid)
);
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(_entMan, args.User, TimeSpan.FromSeconds(currentPath.TreatmentLength * tool.SpeedModifier), ev, uid)
{
BreakOnHandChange = true,
BreakOnDamage = true,
BreakOnWeightlessMove = true,
//BreakOnTargetMove = true, // idk why upstream doesnt allow this
BreakOnUserMove = true,
NeedHand = true
});
}
});
}
}
3 changes: 3 additions & 0 deletions Content.Shared/Damage/DamageModifierSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ public partial class DamageModifierSet

[DataField("flatReductions", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<float, DamageTypePrototype>))]
public Dictionary<string, float> FlatReduction = new();

[DataField("woundReduction")] // TODO: Flat reductions
public float WoundCoefficient = 0;
}
}
Loading

0 comments on commit 7b59a3d

Please sign in to comment.