Files
tbd-station-14/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
Hannah Giovanna Dawson cdbe92d37d Update DamageableSystem to modern standards (#39417)
* Update DamageableSystem to modern standards

* DamageContainerId -> DamageContainerID with lint flag

* Replace strings with protoids

* Make CVar subscription declarations all consistently whitespaced

* ChangeDamage -> TryChangeDamage, cope with C# jank

* Revert event signature changes

* Restore a comment

* Re-add two queries

* Init the queries

* Use appearanceQuery in DamageChanged

* Use damageableQuery in TryChangeDamage

* Use damageableQuery in SetDamageModifierSetId

* Final cleanup, fix sandboxing

* Rectify ExplosionSystem:::ProcessEntity's call to TryChangeDamage

* Re-organize DamageableSystem

* first big fuck you breaking change.

* THATS A LOT OF DAMAGE!!!

* Fix test fails

* test fixes 2

* push it

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-10-27 19:53:04 +00:00

189 lines
6.7 KiB
C#

using System.Text;
using Content.Server.Destructible;
using Content.Server.PowerCell;
using Content.Shared.Speech.Components;
using Content.Shared.Damage.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Speech;
using Robust.Shared.Random;
namespace Content.Server.Speech.EntitySystems;
public sealed class DamagedSiliconAccentSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DamagedSiliconAccentComponent, AccentGetEvent>(OnAccent, after: [typeof(ReplacementAccentSystem)]);
}
private void OnAccent(Entity<DamagedSiliconAccentComponent> ent, ref AccentGetEvent args)
{
var uid = ent.Owner;
if (ent.Comp.EnableChargeCorruption)
{
var currentChargeLevel = 0.0f;
if (ent.Comp.OverrideChargeLevel.HasValue)
{
currentChargeLevel = ent.Comp.OverrideChargeLevel.Value;
}
else if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
{
currentChargeLevel = battery.CurrentCharge / battery.MaxCharge;
}
currentChargeLevel = Math.Clamp(currentChargeLevel, 0.0f, 1.0f);
// Corrupt due to low power (drops characters on longer messages)
args.Message = CorruptPower(args.Message, currentChargeLevel, ent.Comp);
}
if (ent.Comp.EnableDamageCorruption)
{
var damage = FixedPoint2.Zero;
if (ent.Comp.OverrideTotalDamage.HasValue)
{
damage = ent.Comp.OverrideTotalDamage.Value;
}
else if (TryComp<DamageableComponent>(uid, out var damageable))
{
damage = damageable.TotalDamage;
}
// Corrupt due to damage (drop, repeat, replace with symbols)
args.Message = CorruptDamage(args.Message, damage, ent);
}
}
public string CorruptPower(string message, float chargeLevel, DamagedSiliconAccentComponent comp)
{
// The first idxMin characters are SAFE
var idxMin = comp.StartPowerCorruptionAtCharIdx;
// Probability will max at idxMax
var idxMax = comp.MaxPowerCorruptionAtCharIdx;
// Fast bails, would not have an effect
if (chargeLevel > comp.ChargeThresholdForPowerCorruption || message.Length < idxMin)
{
return message;
}
var outMsg = new StringBuilder();
var maxDropProb = comp.MaxDropProbFromPower * (1.0f - chargeLevel / comp.ChargeThresholdForPowerCorruption);
var idx = -1;
foreach (var letter in message)
{
idx++;
if (idx < idxMin) // Fast character, no effect
{
outMsg.Append(letter);
continue;
}
// use an x^2 interpolation to increase the drop probability until we hit idxMax
var probToDrop = idx >= idxMax
? maxDropProb
: (float)Math.Pow(((double)idx - idxMin) / (idxMax - idxMin), 2.0) * maxDropProb;
// Ensure we're in the range for Prob()
probToDrop = Math.Clamp(probToDrop, 0.0f, 1.0f);
if (_random.Prob(probToDrop)) // Lose a character
{
// Additional chance to change to dot for flavor instead of full drop
if (_random.Prob(comp.ProbToCorruptDotFromPower))
{
outMsg.Append('.');
}
}
else // Character is safe
{
outMsg.Append(letter);
}
}
return outMsg.ToString();
}
private string CorruptDamage(string message, FixedPoint2 totalDamage, Entity<DamagedSiliconAccentComponent> ent)
{
var outMsg = new StringBuilder();
// If this is not specified, use the Destructible threshold for destruction or breakage
var damageAtMaxCorruption = ent.Comp.DamageAtMaxCorruption;
if (damageAtMaxCorruption is null)
{
if (!TryComp<DestructibleComponent>(ent, out var destructible))
return message;
damageAtMaxCorruption = _destructibleSystem.DestroyedAt(ent, destructible);
}
// Linear interpolation of character damage probability
var damagePercent = Math.Clamp((float)totalDamage / (float)damageAtMaxCorruption, 0, 1);
var chanceToCorruptLetter = damagePercent * ent.Comp.MaxDamageCorruption;
foreach (var letter in message)
{
if (_random.Prob(chanceToCorruptLetter)) // Corrupt!
{
outMsg.Append(CorruptLetterDamage(letter));
}
else // Safe!
{
outMsg.Append(letter);
}
}
return outMsg.ToString();
}
private string CorruptLetterDamage(char letter)
{
var res = _random.NextDouble();
return res switch
{
< 0.0 => letter.ToString(), // shouldn't be less than 0!
< 0.5 => CorruptPunctuize(), // 50% chance to replace with random punctuation
< 0.75 => "", // 25% chance to remove character
< 1.00 => CorruptRepeat(letter), // 25% to repeat the character
_ => letter.ToString(), // shouldn't be greater than 1!
};
}
private string CorruptPunctuize()
{
const string punctuation = "\"\\`~!@#$%^&*()_+-={}[]|\\;:<>,.?/";
return punctuation[_random.NextByte((byte)punctuation.Length)].ToString();
}
private string CorruptRepeat(char letter)
{
// 25% chance to add another character in the streak
// (kind of like "exploding dice")
// Solved numerically in closed form for streaks of bernoulli variables with p = 0.25
// Can calculate for different p using python function:
/*
* def prob(streak, p):
* if streak == 0:
* return scipy.stats.binom(streak+1, p).pmf(streak)
* return prob(streak-1) * p
* def prob_cum(streak, p=.25):
* return np.sum([prob(i, p) for i in range(streak+1)])
*/
var numRepeats = _random.NextDouble() switch
{
< 0.75000000 => 2,
< 0.93750000 => 3,
< 0.98437500 => 4,
< 0.99609375 => 5,
< 0.99902344 => 6,
< 0.99975586 => 7,
< 0.99993896 => 8,
< 0.99998474 => 9,
_ => 10,
};
return new string(letter, numRepeats);
}
}