Delta-v/Content.Shared/_DV/Body/Systems/PreenableSystem.cs

230 lines
8.3 KiB
C#

using Content.Shared._DV.Body.Components;
using Content.Shared._DV.Body.Events;
using Content.Shared.Body.Components;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Damage.Systems;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Forensics.Systems;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using Content.Shared.StatusEffect;
using Content.Shared.Verbs;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Body.Systems;
public sealed class PreenableSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedForensicsSystem _forensics = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly SharedChatSystem _chat = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PreenableComponent, GetVerbsEvent<Verb>>(AddVerb);
SubscribeLocalEvent<PreenableComponent, PreeningEvent>(OnPreened);
SubscribeLocalEvent<PreenableComponent, DamageChangedEvent>(OnDamaged);
SubscribeLocalEvent<PreenableComponent, DamageModifyEvent>(OnDamageModify);
SubscribeLocalEvent<PreenableComponent, ComponentInit>(OnCompInit);
}
private void OnCompInit(Entity<PreenableComponent> ent, ref ComponentInit args)
{
ent.Comp.CurrentFeathers = ent.Comp.MaximumFeathers;
Dirty(ent);
}
private void AddVerb(Entity<PreenableComponent> ent, ref GetVerbsEvent<Verb> args)
{
if (!args.CanInteract)
return;
// can't preen with no feathers
if (ent.Comp.CurrentFeathers <= 0)
return;
var user = args.User;
Verb verb = new()
{
Act = () => AttemptDoAfter(ent, user),
Text = Loc.GetString(ent.Comp.PreeningVerbString),
};
args.Verbs.Add(verb);
}
private void AttemptDoAfter(Entity<PreenableComponent> ent, EntityUid userUid)
{
var doArgs = new DoAfterArgs(EntityManager, userUid, 5f, new PreeningEvent(), ent, ent)
{
BreakOnMove = true,
BreakOnDamage = true,
};
if (userUid == ent.Owner)
{
_popup.PopupClient(Loc.GetString(ent.Comp.SelfPreeningMessage), ent, ent);
}
else
{
_popup.PopupEntity(Loc.GetString(ent.Comp.GettingPreenedMessage, ("preener", Identity.Entity(userUid, EntityManager))), ent, ent, PopupType.Medium);
_popup.PopupClient(Loc.GetString(ent.Comp.PreeningOtherMessage, ("preenee", Identity.Entity(ent, EntityManager))), userUid, userUid);
}
_doAfter.TryStartDoAfter(doArgs);
}
private void OnPreened(Entity<PreenableComponent> ent, ref PreeningEvent args)
{
if (args.Cancelled || args.Handled)
return;
if (ent.Comp.CurrentFeathers <= 0)
return;
var feather = SpawnFeather(ent, false);
_hands.TryPickupAnyHand(args.User, feather);
}
private void OnDamaged(Entity<PreenableComponent> ent, ref DamageChangedEvent args)
{
if (args.DamageDelta == null || ent.Comp.ValidDamageGroups == null)
return;
if (ent.Comp.CurrentFeathers <= 0)
return;
var totalApplicableDamage = FixedPoint2.Zero;
foreach (var (group, value) in args.DamageDelta.GetDamagePerGroup(_prototype))
{
if (!ent.Comp.ValidDamageGroups.Contains(group))
continue;
totalApplicableDamage += value;
}
if (totalApplicableDamage <= ent.Comp.ShedDamageThreshold)
return;
// predicted randomness is a truly evil thing
var rand = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent));
var randomDouble = rand.NextDouble();
var triggerChance = totalApplicableDamage * ent.Comp.ShedScalingChance;
if (randomDouble >= triggerChance)
return;
var feather = SpawnFeather(ent, true);
// apply a random impulse so it's flying off the body. similar code to GibbingSystem
var scatterVector = rand.NextAngle().ToVec() * (rand.NextFloat(10, 40));
_physics.ApplyLinearImpulse(feather, scatterVector);
_physics.ApplyAngularImpulse(feather, rand.NextFloat(-30, 30));
// update name/desc for increased validness
var meta = MetaData(feather);
_metaData.SetEntityName(feather, Loc.GetString(ent.Comp.FeatherBloodiedNameString, ("item", Name(feather))), meta);
_metaData.SetEntityDescription(feather, Loc.GetString(ent.Comp.FeatherBloodiedDescString), meta);
Dirty(feather, meta);
// yeeeowch!
_popup.PopupClient(Loc.GetString(ent.Comp.DroppedFeatherString), ent, ent, PopupType.MediumCaution);
_chat.TryEmoteWithoutChat(ent, ent.Comp.ScreamEmote);
// old StatusEffects is obsolete, however Adrenaline has not been moved over to the new system yet
_statusEffects.TryAddStatusEffect(ent, "Adrenaline", TimeSpan.FromSeconds(3), true);
}
private void OnDamageModify(Entity<PreenableComponent> ent, ref DamageModifyEvent args)
{
if (ent.Comp.VulnerabilityModifier == null)
return;
// zero vulnerability at max feathers, full vulnerability at 0 feathers
var vulnerabilityModifier = 1f - (ent.Comp.CurrentFeathers / (float)ent.Comp.MaximumFeathers);
var damageSpecifier = new DamageModifierSet
{
Coefficients = new Dictionary<string, float>(ent.Comp.VulnerabilityModifier.Coefficients),
};
foreach (var key in damageSpecifier.Coefficients.Keys)
{
damageSpecifier.Coefficients[key] = 1f + ((damageSpecifier.Coefficients[key] - 1f) * vulnerabilityModifier);
}
args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, damageSpecifier);
}
private EntityUid SpawnFeather(Entity<PreenableComponent> ent, bool bloody)
{
var feather = PredictedSpawnAtPosition(ent.Comp.FeatherPrototype.Id, Transform(ent).Coordinates);
if (TryComp<HumanoidAppearanceComponent>(ent, out var appearance))
{
_appearance.SetData(feather, FeatherVisuals.FeatherColor, appearance.SkinColor);
}
// best be careful, no cleaning this
_forensics.TransferDna(feather, ent, false);
ent.Comp.CurrentFeathers -= 1;
ent.Comp.ReplenishTime = _timing.CurTime + ent.Comp.ReplenishDelay;
Dirty(ent);
if (!bloody || !TryComp<BloodstreamComponent>(ent, out var bloodstream) || bloodstream.BloodSolution == null)
return feather;
var solution = bloodstream.BloodSolution.Value.Comp.Solution;
_appearance.SetData(feather, FeatherVisuals.BloodColor, solution.GetColor(_prototype));
return feather;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
var preenableQuery = EntityQueryEnumerator<PreenableComponent>();
while (preenableQuery.MoveNext(out var uid, out var preenable))
{
if (preenable.ReplenishTime == null || !(preenable.ReplenishTime <= _timing.CurTime))
continue;
if (preenable.CurrentFeathers >= preenable.MaximumFeathers)
continue;
preenable.CurrentFeathers += 1;
if (preenable.CurrentFeathers >= preenable.MaximumFeathers)
{
preenable.ReplenishTime = null;
continue;
}
preenable.ReplenishTime = _timing.CurTime + preenable.ReplenishDelay;
}
}
}